# -*- coding: utf-8 -*-
"""细化方法中参数的推理，用于domain.py的调用"""

__author__ = 'Yu XH'

import math
from random import choice
from myAI.hexmap.hex import *
from myAI.attdamage.handle_shoot import *
from myAI.const import *
from myAI.hexmap.handle_map import *

CM_HUNT_PERIOD = 200


def is_moving(obj):
    """ 2020-4-24
    是否正在机动的判断
    """
    if obj['speed'] > 0:
        return True
    else:
        return False


def has_stopped(obj):
    """ 2020-4-25
    是否已经停止的判断
    """
    if obj['stop'] == 1:
        return True
    else:
        return False


def get_mod(obj_id, myAgent):
    """ 2020-5-19
    确定算子机动的类型，gen_move_route函数中的参数
    """
    obj = myAgent.get_bop(obj_id)
    if obj['type'] == BopType.Infantry:
        return MoveType.Walk
    elif obj['type'] == BopType.Vehicle:
        return MoveType.Maneuver
    elif obj['type'] == BopType.Aircraft:
        return MoveType.Fly
    else:
        raise Exception('不能确定机动类型')


def guide_ability(obj_id, myAgent):
    """ 2020-7-23
    确定算子是否有引导射击能力，更新Drone
    """
    opr = myAgent.get_bop(obj_id)
    if opr['sub_type'] in [BopType.UCV, BopType.Man]:
        if (opr['guide_ability'] == 1):  # 如果有引导射击的属性
            if opr['launcher']:  # 如果有“所属车辆”
                if myAgent.get_bop(opr['launcher']):  # 如果所属车辆配有“重型导弹73”
                    if myAgent.get_bop(opr['launcher'])['remain_bullet_nums'][100] > 0:  # 如果所属车辆还有剩余的重型导弹的弹药
                        return True
    if opr['sub_type'] == BopType.Drone:
        enemy_ids = opr['see_enemy_bop_ids']
        for ene_id in enemy_ids:
            enemy = myAgent.get_bop(ene_id)
            for ifv in myAgent.get_our_oprs():
                if ifv['sub_type'] == BopType.IFV:
                    if ifv['remain_bullet_nums'][BulletType.HeavyMissile] > 0:
                        if 1 < cal_distance(enemy['cur_hex'], ifv['cur_hex']) <= 20:
                            return True
    return False


def guided_ability(obj_id, myAgent):
    """ 2020-4-20
    确定算子是否有被引导射击能力，检查对象是战车
    """
    if myAgent.get_bop(obj_id)['remain_bullet_nums'][100] <= 0:  # 如果所属车辆耗尽重型导弹的弹药
        return False
    for opr in myAgent.observation['operators']:  # 找出任意一个我方能引导当前算子的
        if opr['color'] == myAgent.color:
            if (opr['guide_ability'] == 1):  # 如果有引导射击的属性
                if opr['launcher'] == obj_id:  # 如果有“所属车辆”
                    return True
    return False


def shoot_ability(obj_id, myAgent):
    """ 2020-4-15
    确定算子opr是否有射击能力
    """
    opr = myAgent.get_bop(obj_id)
    if max(opr['remain_bullet_nums'].values()) > 0 or guide_ability(obj_id, myAgent):  # 如果有剩余弹药，或有引导射击能力
        return True
    else:
        return False


def move_n_shoot_ability(opr):
    """ 2020-5-8
    检查算子是否具有“行进间射击”能力
    """
    if opr['sub_type'] == 0 and opr['remain_bullet_nums'][0] > 0:  # 必须是坦克，同时有能力射击
        return True
    else:
        return False


def in_enemies_range(obj_id, myAgent):
    """ 2020-5-14
    判断算子obj当前是否在敌人的火力范围内
    """
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    obj_hex = obj['cur_hex']
    enemy_ids = obj['see_enemy_bop_ids']
    if not enemy_ids:
        return False
    for ene_id in enemy_ids:
        enemy = myAgent.get_bop(ene_id)
        distance = cal_distance(obj_hex, enemy['cur_hex'])
        weapons_id = enemy['carry_weapon_ids']
        for wp_id in weapons_id:
            dist_wp = get_weapon(wp_id).shoot_range[obj_type]
            if distance < dist_wp:
                return True
    return False


def confirm_enemies(myAgent, radius):
    """ 2020-4-30
    找出所有敌方算子，误差范围在radius距离以内
    """
    enemy_ids = []
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        if enemy_info['spread_flag']:
            enemy_id = enemy_info['obj_id']
            speed = enemy_info['operator']['basic_speed'] / 3.6
            final_step = enemy_info['step']
            cur_step = myAgent.observation['time']['cur_step']
            if (cur_step - final_step) * speed / 200 < radius + 1:  # 当radius==0时，只要行进没有达到200m，表示位于本格
                enemy_ids.append(enemy_id)
    return enemy_ids


def predict_enemy_locs(myAgent, enemy_info):
    """ 2020-5-26
    预测敌方算子（由myAgent.factionAI.enemies获取）的各点位上的概率
    :return: n_hex长度的向量，表示位于向量索引对应六角格的概率
    """
    discover_step = enemy_info['step']
    discover_loc = enemy_info['operator']['cur_hex']
    discover_loc_ind = mapid_2_matind(discover_loc, myAgent.get_map_size())
    ene_type = enemy_info['operator']['type']
    unsee_period = myAgent.observation['time']['cur_step'] - discover_step
    if ene_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
        loc_vec = (cost_matrix[discover_loc_ind] <= unsee_period) + 0  # 0-1向量
    elif ene_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        loc_vec = (cost_matrix[discover_loc_ind] <= unsee_period) + 0  # 0-1向量
    elif ene_type == BopType.Aircraft:
        dist_matrix = myAgent.my_map['dist_matrix']
        ene_speed = enemy_info['operator']['basic_speed'] / 3.6
        radius = unsee_period * ene_speed / 200.
        loc_vec = (dist_matrix[discover_loc_ind] <= radius) + 0  # 0-1向量
    else:
        raise Exception('目标算子类型输入错误！')
    sum_loc_vec = np.sum(loc_vec)
    sum_loc_vec = sum_loc_vec * 1. if sum_loc_vec > 0 else 1.
    loc_prob_vec = loc_vec / sum_loc_vec
    return loc_prob_vec


def get_rival_weapons(myAgent, obj_type):
    """ 2020-5-24
    输出敌方的所有可用武器
    """
    weapon_ids = []
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        if enemy_info['operator']['sub_type'] in [BopType.Artillery, BopType.CruiseMissile]:
            continue
        carry_weapon_ids = enemy_info['operator']['carry_weapon_ids']
        remain_bullet_nums = enemy_info['operator']['remain_bullet_nums']
        for wp_id in carry_weapon_ids:
            weapon = get_weapon(wp_id)
            if remain_bullet_nums[weapon.type] > 0 and weapon.shoot_range[obj_type] > 0:
                weapon_ids.append(wp_id)
    return list(set(weapon_ids))


def shoot_check(obj_id, myAgent):
    """ 2020-8-1
    确定算子opr当前状态下是否可射击
    """
    observation = myAgent.observation
    if obj_id not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][obj_id]
    if ActionType.GuideShoot in opr_v_a.keys():  # 如果算子opr当前可以执行引导射击
        for action in opr_v_a[ActionType.GuideShoot]:
            tar_obj = myAgent.get_bop(action['target_obj_id'])
            if not tar_obj:
                continue
            if tar_obj['sub_type'] in [BopType.Tank, BopType.IFV]:
                return True
    if ActionType.Shoot in opr_v_a.keys():  # 如果算子opr当前可以执行射击
        obj = myAgent.get_bop(obj_id)
        for action in opr_v_a[ActionType.Shoot]:
            tar_obj = myAgent.get_bop(action['target_obj_id'])
            if not tar_obj:
                continue
            if tar_obj['sub_type'] == BopType.Artillery:
                continue
            if obj['sub_type'] == BopType.Helicopter:
                if tar_obj['sub_type'] in [BopType.Tank, BopType.IFV]:
                    return True
            return True
    return False


def shoot_target_check(obj_id, tar_id, observation):
    """ 2020-4-24
    确定算子opr当前状态下是否可射击目标算子tar
    """
    if obj_id not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][obj_id]
    if 2 in opr_v_a.keys():
        if tar_id in [x['target_obj_id'] for x in opr_v_a[2]]:
            return True
    if 9 in opr_v_a.keys():
        if tar_id in [x['target_obj_id'] for x in opr_v_a[9]]:
            return True
    return False


def occupy_check(opr, observation):
    """ 2020-4-22
    确定算子opr当前是否可以执行占领动作
    """
    if opr['obj_id'] not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][opr['obj_id']]
    if ActionType.Occupy in opr_v_a.keys():  # 如果当前可夺控
        return True
    else:
        return False


def jm_shoot_check(opr, observation):
    """2020-7-1
    确定算子opr当前是否可以执行间瞄射击动作
    """
    if opr['obj_id'] not in observation['valid_actions'].keys():
        return False
    opr_v_a = observation['valid_actions'][opr['obj_id']]
    if ActionType.JMPlan in opr_v_a.keys():
        return True
    else:
        return False


def get_on_check(ifv_id, passenger_id, observation):
    """ 2020-6-18
        确定当前passenger是否可以上车
        """
    if passenger_id in observation['valid_actions'].keys():
        if ActionType.GetOn in observation['valid_actions'][passenger_id].keys():
            if ifv_id in [action['target_obj_id'] for action in
                          observation['valid_actions'][passenger_id][ActionType.GetOn]]:
                return True
    return False


def get_off_check(ifv_id, passenger_id, observation):
    """ 2020-4-18
    确定当前passenger是否可以从ifv下车
    """
    if ifv_id in observation['valid_actions'].keys():
        if ActionType.GetOff in observation['valid_actions'][ifv_id].keys():
            if passenger_id in [tar['target_obj_id'] for tar in observation['valid_actions'][ifv_id][4]]:
                return True
    return False


def cm_getoff_check(obj_id, myAgent):
    """ 2020-4-26
    确定当前巡飞弹是否可以下车
    """
    # 下车条件：找出巡飞弹的装载车辆veh，如果当前天上没有veh发射的其它巡飞弹，即可下车
    veh_id = myAgent.get_bop(obj_id)['car']
    for opr in myAgent.observation['operators']:
        if opr['sub_type'] == BopType.CruiseMissile and opr['launcher'] == veh_id:
            return False
    return True


def change_state_check(obj_id, tar_state, observation):
    """ 2020-4-19
    确定算子当前是否可以切换到tar_state的状态
    """
    if obj_id in observation['valid_actions'].keys():
        if ActionType.ChangeState in observation['valid_actions'][obj_id].keys():
            if tar_state in [ts['target_state'] for ts in observation['valid_actions'][obj_id][6]]:
                return True
    return False


def cm_shoot_check(obj_id, myAgent):
    """ 2020-5-19
    巡飞弹是否实施射击的判断，输出射击flag和选中的射击目标ID
    """
    cm = myAgent.get_bop(obj_id)
    if not cm:
        return None
    if (myAgent.get_bop(obj_id)['alive_remain_time'] <= CM_HUNT_PERIOD) or \
            (myAgent.observation['time']['cur_step'] >= myAgent.max_step - CM_HUNT_PERIOD):
        remain_time = min(cm['alive_remain_time'], myAgent.max_step - myAgent.observation['time']['cur_step'])
        dist = int(remain_time * 100 / 3.6 / 200) - 2  # 确定打击半径
        enemy_ids = confirm_enemies(myAgent, 2)
        if enemy_ids:
            candidates = []
            min_dist = 1000
            closest_enemy_outside = None
            for id in enemy_ids:
                enemy = myAgent.agenda.get_enemy_info(id)['operator']
                dist_to_enemy = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                if dist_to_enemy <= dist:
                    candidates.append(id)
                elif dist_to_enemy < min_dist:
                    min_dist = dist_to_enemy
                    closest_enemy_outside = id
            if not candidates and closest_enemy_outside:  # 选择最近的进行打击
                return True, closest_enemy_outside
            # 为敌方算子分等级：压制的步兵、重型装甲车辆、未压制的步兵、复合装甲车辆
            candidates_type1 = []  # 存储所有压制的步兵
            candidates_type2 = []  # 存储所有重型装甲车辆
            candidates_type3 = []  # 存储所有未压制的步兵
            candidates_type4 = []  # 存储所有复合装甲车辆
            candidates_type5 = []  # 存储所有其它敌方单位
            for e_id in candidates:
                enemy_info = myAgent.agenda.get_enemy_info(e_id)
                enemy = enemy_info['operator']
                if enemy['type'] == BopType.Infantry:  # 步兵
                    if enemy['keep'] == 1:
                        distance = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                        elapse = myAgent.observation['time']['cur_step'] - enemy_info['step']
                        if enemy['keep_remain_time'] - elapse > distance * 7.2:
                            candidates_type1.append(enemy)
                        else:
                            candidates_type2.append(enemy)
                    else:
                        candidates_type2.append(enemy)
                else:  # 车辆
                    if enemy['armor'] != ArmorType.Composit:
                        candidates_type3.append(enemy)
                    elif enemy['armor'] == ArmorType.Composit:
                        candidates_type4.append(enemy)
                    else:
                        candidates_type5.append(enemy)
            # 根据顺序确定打击对象，同类算子依据距离优先
            if candidates_type1:
                tar = min(candidates_type1,
                          key=lambda x:
                          cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                          (myAgent.observation['time']['cur_step'] -
                           myAgent.agenda.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )  # 根据距离上限选择，距离上限是指可能距我最远的
                return True, tar['obj_id']
            if candidates_type2:
                tar = min(candidates_type2, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.agenda.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type3:
                tar = min(candidates_type3, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.agenda.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type4:
                tar = min(candidates_type4, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.agenda.rae_situation.getEnemyInfoByID(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type5:
                tar = min(candidates_type5, key=lambda x:
                cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                (myAgent.observation['time']['cur_step'] -
                 myAgent.agenda.get_enemy_info(x['obj_id'])['step']) * (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
        else:  # 找不到敌方算子
            return False, None
    elif myAgent.get_bop(obj_id)['alive_remain_time'] <= 600:
        # 判断当前巡飞弹是所在战车发射的第1个
        first_flag = False
        car_id = myAgent.get_bop(obj_id)['launcher']
        for passenger_id in myAgent.get_bop(car_id)['passenger_ids']:
            if myAgent.get_bop(passenger_id)['sub_type'] == BopType.CruiseMissile:
                first_flag = True
        if first_flag:  # 如果是第1个
            enemy_ids = myAgent.get_bop(obj_id)['see_enemy_bop_ids']
            for id in enemy_ids:
                if cal_distance(myAgent.get_bop(obj_id)['cur_hex'], myAgent.get_bop(id)['cur_hex']) > 2:
                    enemy_ids.remove(id)
                if myAgent.get_bop(id)['type'] == BopType.Infantry:  # 步兵
                    if myAgent.get_bop(obj_id)['cur_hex'] == myAgent.get_bop(id)['cur_hex']:
                        enemy_ids.remove(id)
            if enemy_ids:
                for id in enemy_ids:
                    curr_terrain = get_terrain_info(myAgent.map.get_map_data(), myAgent.get_bop(id)['cur_hex'])
                    if myAgent.get_bop(id)['type'] == BopType.Infantry \
                            and myAgent.get_bop(id)['keep'] == 1 \
                            and curr_terrain != TerrainType.Forest:  # 步兵、压制、非从林地
                        return True, id
                    if myAgent.get_bop(id)['type'] == BopType.Vehicle \
                            and (myAgent.get_bop(id)['move_state'] == MoveState.March or
                                 myAgent.get_bop(id)['stack'] == 1) \
                            and curr_terrain == TerrainType.OpenLand:
                        return True, id
                return False, None
            else:
                return False, None
    return False, None


def cm_shoot_check_2nd(obj_id, myAgent):
    """ 2020-7-6 用于群队
    巡飞弹是否实施射击的判断，输出射击flag和选中的射击目标ID
    """
    # === 计算当前在空中的巡飞弹数量 ===
    cur_step = myAgent.observation['time']['cur_step']
    cm = myAgent.get_bop(obj_id)
    if not cm:
        return None
    cm_count = 0
    for opr in myAgent.get_our_oprs():
        if opr['sub_type'] == BopType.CruiseMissile and opr['on_board'] == 0:
            cm_count += 1
    # === 最后生命的一击 ===
    if (cm['alive_remain_time'] <= CM_HUNT_PERIOD) or (cur_step >= myAgent.max_step - CM_HUNT_PERIOD):
        remain_time = min(cm['alive_remain_time'], myAgent.max_step - cur_step)
        dist = int(remain_time * 100 / 3.6 / 200) - 2  # 确定打击半径
        enemy_ids = confirm_enemies(myAgent, 2)
        if enemy_ids:
            candidates = []
            closest_enemy_outside = None
            min_dist = 1000
            for id in enemy_ids:
                enemy = myAgent.agenda.get_enemy_info(id)['operator']
                dist_to_enemy = cal_distance(myAgent.get_bop(obj_id)['cur_hex'], enemy['cur_hex'])
                if dist_to_enemy <= dist:
                    candidates.append(id)
                elif dist_to_enemy < min_dist:
                    min_dist = dist_to_enemy
                    closest_enemy_outside = id
            if not candidates:  # 选择最近的进行打击
                if closest_enemy_outside:
                    return True, closest_enemy_outside
                else:
                    return False, None
            # 为敌方算子分等级：压制的步兵、无有利修正步兵、重型装甲车辆、复合装甲车辆
            candidates_type1 = []  # 存储所有压制的步兵
            candidates_type2 = []  # 存储无有利修正步兵
            candidates_type3 = []  # 存储其它步兵
            candidates_type4 = []  # 存储战车
            candidates_type5 = []  # 存储坦克
            for e_id in candidates:
                enemy_info = myAgent.agenda.get_enemy_info(e_id)
                enemy = enemy_info['operator']
                if enemy['type'] == BopType.Infantry:  # 步兵
                    keep_flag = False
                    if enemy['keep'] == 1:
                        candidates_type1.append(enemy)
                        keep_flag = True
                    if not keep_flag:
                        if enemy['move_state'] == MoveState.Shelter:
                            candidates_type3.append(enemy)
                        elif enemy['stop'] != 1:
                            candidates_type3.append(enemy)
                        elif get_terrain_info(myAgent.map.get_map_data(), enemy['cur_hex']) in [TerrainType.Forest,
                                                                                                TerrainType.Resident]:
                            candidates_type3.append(enemy)
                        else:
                            candidates_type2.append(enemy)
                else:  # 车辆
                    if enemy['sub_type'] == BopType.IFV:
                        candidates_type4.append(enemy)
                    elif enemy['sub_type'] == BopType.Tank:
                        candidates_type5.append(enemy)
            # 根据顺序确定打击对象，同类算子依据距离优先
            if candidates_type1:
                tar = min(candidates_type1,
                          key=lambda x: cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                                        (cur_step - myAgent.agenda.get_enemy_info(x['obj_id'])['step']) *
                                        (x['basic_speed'] / 3.6) / 200
                          )  # 根据距离上限选择，距离上限是指可能距我最远的
                return True, tar['obj_id']
            if candidates_type2:
                tar = min(candidates_type2,
                          key=lambda x: cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                                        (cur_step - myAgent.agenda.get_enemy_info(x['obj_id'])['step']) *
                                        (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type3:
                tar = min(candidates_type3,
                          key=lambda x: cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                                        (cur_step - myAgent.agenda.get_enemy_info(x['obj_id'])['step']) *
                                        (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type4:
                tar = min(candidates_type4,
                          key=lambda x: cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                                        (cur_step - myAgent.agenda.get_enemy_info(x['obj_id'])['step']) *
                                        (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
            if candidates_type5:
                tar = min(candidates_type5,
                          key=lambda x: cal_distance(myAgent.get_bop(obj_id)['cur_hex'], x['cur_hex']) +
                                        (cur_step - myAgent.agenda.get_enemy_info(x['obj_id'])['step']) *
                                        (x['basic_speed'] / 3.6) / 200
                          )
                return True, tar['obj_id']
        else:  # 找不到敌方算子
            return False, None
    # === 把握机会的一击 ===
    elif myAgent.get_bop(obj_id)['alive_remain_time'] <= 700 and cm_count > 1:
        # 炮兵是否可以射击？boom_time < 150不能射击
        for jm_info in myAgent.observation['jm_points']:
            if jm_info['color'] == myAgent.color:
                if jm_info['status'] == 1 and jm_info['boom_time'] < 150:
                    return False, None
        dist_threshold = 10
        cm_loc = cm['cur_hex']
        candidates_level_1, candidates_level_2 = [], []  # 无有利修正的步兵，只有一种有利修正的步兵
        for enemy_info in myAgent.agenda.rae_situation.enemies:
            if not enemy_info['spread_flag']:
                continue
            if np.max(enemy_info['pos_probs']) < 0.5:
                continue
            enemy = enemy_info['operator']
            if enemy['type'] == BopType.Infantry and enemy['keep'] == 0:  # 压制的步兵交给炮兵
                if 0 < cal_distance(cm_loc, enemy['cur_hex']) <= dist_threshold:
                    curr_terrain = get_terrain_info(myAgent.map.get_map_data(), enemy['cur_hex'])
                    rect_vec = [0] * 3
                    if curr_terrain in [TerrainType.Forest, TerrainType.Resident]:
                        rect_vec[0] = 1
                    if enemy['stop'] == 0:
                        rect_vec[1] = 1
                    if enemy['move_state'] == MoveState.Shelter:
                        rect_vec[2] = 1
                    if sum(rect_vec) <= 0:
                        candidates_level_1.append(enemy)
                    elif sum(rect_vec) <= 1:
                        candidates_level_2.append(enemy)
        if candidates_level_1:
            tar = min(candidates_level_1, key=lambda x: cal_distance(cm_loc, x['cur_hex']))
        elif candidates_level_2:
            tar = min(candidates_level_2, key=lambda x: cal_distance(cm_loc, x['cur_hex']))
        else:
            tar = None
        if tar:
            return True, tar['obj_id']
    return False, None


def rapid_march_check(obj, move_loc):
    """ 2020-5-1
    是否进行快速机动的判断
    > 后期可以加入任务完成紧迫性的判断
    """
    if obj['type'] == BopType.Aircraft:  # 空中算子
        return True
    if obj['sub_type'] == BopType.Artillery:  # 炮兵
        return True
    if obj['sub_type'] == BopType.Tank:
        return True
    if obj['type'] == BopType.Vehicle:  # 车辆
        if cal_distance(obj['cur_hex'], move_loc) <= 3:  # 短距离机动
            return True
        else:
            return False
    if obj['type'] == BopType.Infantry:  # 人员
        return True


def force_stop_check(obj_id, move_loc, myAgent):
    """ 2020-5-9
    是否进行机动停止的判断
    """
    obj = myAgent.get_bop(obj_id)
    if obj['type'] == 3:  # 空中算子
        return True
    if not obj['move_path']:
        return False
    end_loc = obj['move_path'][-1]
    if move_loc == end_loc:
        return False
    cur_loc = obj['cur_hex']  # 需要完成当前的机动
    mod = 2 if obj['type'] == 1 else 0  # 机动方式，不考虑行军
    new_path = myAgent.map.gen_move_route(cur_loc, move_loc, mod)
    new_path_time = calc_route_time(obj, new_path, myAgent.my_map['cost_data'])
    old_path_1 = obj['move_path']
    old_path_2 = myAgent.map.gen_move_route(end_loc, move_loc, mod)
    old_path = old_path_1 + old_path_2
    old_path_time = calc_route_time(obj, old_path, myAgent.my_map['cost_data'])
    if new_path_time + 75 < old_path_time:
        return True
    else:
        return False


def move_n_shoot_check(obj, observation):
    """ 2020-5-9
    判断当前是否可以实施行进间射击
    """
    opr_v_a = observation['valid_actions'][obj['obj_id']]
    if 1 in opr_v_a.keys() and 2 in opr_v_a.keys():
        return True
    if 1 in opr_v_a.keys() and obj['weapon_cool_time'] <= 40:
        return True
    return False


def vehicle_in_sight_check(obj_id, myAgent):
    """判断当前算子视野内是否有坦克或者战车（引导射击用）"""
    obj = myAgent.get_bop(obj_id)
    for ene_id in obj['see_enemy_bop_ids']:
        enemy = myAgent.get_bop(ene_id)
        if enemy['sub_type'] in [BopType.IFV, BopType.Tank]:
            return True
    return False


def hit_check(obj_id, tar_id, move_shoot, myAgent):
    """我方算子是否可以有效“损伤”敌方算子的判断"""
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    cur_hex_ind = mapid_2_matind(obj['cur_hex'], map_size)
    enemy = myAgent.agenda.get_enemy(tar_id)
    pos_probs = myAgent.agenda.get_enemy_info(tar_id)['pos_probs']
    tar_type = enemy['type']
    tar_area_ind = np.arange(n_hexes)[pos_probs > 0]
    if move_shoot:  # 行进间射击的情况
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        fire_area_bool = (cost_matrix[cur_hex_ind] <= 40)
        fire_area = np.arange(n_hexes)[fire_area_bool]
        fire_area_att_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                                     fire_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                                     special_shoot='move_shoot')
        fire_area_attackabilities = fire_area_att_levels @ pos_probs[pos_probs > 0]
        attackabilities = np.max(fire_area_attackabilities)
    else:  # 普通射击的情况
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              cur_hex_ind, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                              special_shoot=None)
        attackabilities = attack_levels @ pos_probs[pos_probs > 0]
    return True if attackabilities > 0 else False


def hit_check_any_enemy(obj_id, move_shoot, myAgent):
    """我方算子当前是否可能实施有效射击的判断"""
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    n_hexes = map_size['row'] * map_size['col']
    if move_shoot:
        special_shoot = 'move_shoot'
    else:
        special_shoot = None
    obj = myAgent.get_bop(obj_id)
    obj_loc = obj['cur_hex']
    obj_loc_ind = mapid_2_matind(obj_loc, map_size)
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        enemy = enemy_info['operator']
        enemy_loc = enemy['cur_hex']
        enemy_loc_ind = mapid_2_matind(enemy_loc, map_size)
        if abs(np.max(enemy_info['pos_probs']) - enemy_info['pos_probs'][enemy_loc_ind]) > 1e-8:  # 长久没有更新的敌方算子，不投入计算
            continue
        shoot_radius = get_shoot_radius(obj['carry_weapon_ids'], obj['remain_bullet_nums'], enemy['type'],
                                        special_shoot)
        if dist_matrix[obj_loc_ind, enemy_loc_ind] <= shoot_radius:
            if obj['type'] == BopType.Aircraft and enemy['type'] == BopType.Aircraft:
                see_matrix = myAgent.my_map['see_matrix'][1]
            elif obj['type'] != BopType.Aircraft and enemy['type'] != BopType.Aircraft:
                see_matrix = myAgent.my_map['see_matrix'][0]
            else:
                see_matrix = myAgent.my_map['see_matrix'][2]
            if move_shoot:
                cost_matrix = myAgent.my_map['cost_matrix'][0]
                fire_area_bool = (cost_matrix[obj_loc_ind] <= 40)
                fire_area = np.arange(n_hexes)[fire_area_bool]
                see_flag = see_matrix[:, enemy_loc_ind][fire_area]
                if np.any(see_flag):
                    return True
            else:
                if enemy['obj_id'] in obj['see_enemy_bop_ids']:
                    return True
    return False


def jm_salvo_check(arti_id, tar_loc, myAgent):
    """2020-7-1
    判断是否完成齐射动作
    """
    done_flag = False
    for jm_info in myAgent.observation['jm_points']:
        if jm_info['obj_id'] and jm_info['status'] == 0:  # 正在飞行的才算
            if jm_info['obj_id'] == arti_id and jm_info['pos'] == tar_loc:
                done_flag = True
                break
    return done_flag


def aircraft_in_shoot_range(obj_id, myAgent):
    """判断obj_id的攻击范围中是否有直升机"""
    obj = myAgent.get_bop(obj_id)
    if not obj:
        return False
    shoot_radius = get_shoot_radius(obj['carry_weapon_ids'], obj['remain_bullet_nums'], BopType.Aircraft)
    for ene_id in obj['see_enemy_bop_ids']:
        enemy = myAgent.get_bop(ene_id)
        if enemy['sub_type'] == BopType.Helicopter:
            if cal_distance(obj['cur_hex'], enemy['cur_hex']) <= shoot_radius:
                return True
    return False


def enemies_in_area(area, myAgent):
    """ 2020-4-30
    判断区域area中是否有敌方单位
    """
    enemy_ids = confirm_enemies(myAgent, 0)
    if not enemy_ids:  # 当前看不到敌人
        return False
    for id in enemy_ids:
        if myAgent.agenda.get_enemy_info(id)['operator']['cur_hex'] in area:
            return True
    return False


def enemy_around_city(city_loc, myAgent):
    """ 2020-6-24
    判断夺控点及周边六角格是否有敌方单位
    """
    map_size = myAgent.get_map_size()
    area = get_range(city_loc, 1, map_size)
    enemies = myAgent.agenda.rae_situation.enemies
    for enemy_info in enemies:
        enemy_loc = enemy_info['operator']['cur_hex']
        if enemy_loc in area:
            return True
    return False


def get_remain_path(path, loc):
    """ 2020-4-26
    返回自loc以后的机动路线
    """
    path_remained = []
    for pt in path[::-1]:
        if pt != loc:
            path_remained.insert(0, pt)
        else:
            break
    return path_remained


#
# def infer_fire_loc(opr, area, myAgent):
#     """为算子opr推理出针对目标区域area的攻击点"""
#     if not area:  # 如果目标区域为空
#         return opr['cur_hex']
#
#     c_hex = closest_hex(opr['cur_hex'], area)
#     c_hex_offset = HexOffset(c_hex // 100, c_hex % 100)
#     directions = c_hex_offset.get_wedge_direction(HexOffset(opr['cur_hex'] // 100, opr['cur_hex'] % 100))
#
#     r_c = 10
#     while r_c > 5:
#         for direction in directions:
#             arc = c_hex_offset.get_arc(r_c, direction)
#             for hex in arc:
#                 if myAgent.map.can_see(hex, c_hex_offset, 0):  # 系统自带可是判断函数
#                     return hex
#         r_c = 10 + (10 - r_c) + 1 if r_c <= 10 else 10 - (r_c - 10)
#     return None


def get_cover_circles(area, radius):
    """ 2020-4-27
    对area的六角格区域，用半径为radius的圆进行全覆盖
    :param area: 六角格ID构成的list
    :param radius: 覆盖的圆半径
    :return: 用于全覆盖的圆心六角格构成的list
    """
    centers = []  # 用于输出
    num = len(area)
    dists = np.zeros((num, num), dtype=float)
    flags = np.ones(num, dtype=int)
    for m in range(num):
        for n in range(num):
            if m != n:
                dists[m, n] = cal_distance(area[m], area[n])
    while flags.sum() > 0:
        max_ind = (dists < 3).sum(1).argmax()
        inner_inds = dists[max_ind, :] < 3
        dists[inner_inds, :] = 1000
        dists[:, inner_inds] = 1000
        flags[inner_inds] = 0
        centers.append(area[max_ind])
    return centers


def get_cm_search_target(obj_id, enemies, myAgent):
    """ 2020-6-18 用于分队级别
    确定巡飞弹的搜索对象
    :param enemies: 在分队级想定中就是myAgent.situations.enemies；在群队级想定中需要划分出部分
    """
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    cm = myAgent.get_bop(obj_id)
    cm_loc_ind = mapid_2_matind(cm['cur_hex'], map_size)
    enemies_info = []
    for enemy_info in enemies:
        if enemy_info['spread_flag']:
            enemy = enemy_info['operator']
            if enemy['type'] != BopType.Aircraft:
                enemies_info.append(enemy_info)
    if not enemies_info:
        return None
    n_enemies = len(enemies_info)
    enemy_locs = np.zeros(n_enemies, dtype=int)
    enemy_uncertainties = np.zeros(n_enemies, dtype=float)  # 敌方算子的位置不确定性
    for m in range(n_enemies):
        enemy_info = enemies_info[m]
        enemy = enemy_info['operator']
        enemy_locs[m] = mapid_2_matind(enemy['cur_hex'], map_size)
        enemy_uncertainties[m] = 1 - np.max(enemy_info['pos_probs'])
    dists_2_enemies = dist_matrix[cm_loc_ind, enemy_locs]
    max_dist = np.max(dists_2_enemies)
    if max_dist < 1e-8:
        proximities = np.ones(n_enemies, dtype=float)
    else:
        proximities = (max_dist - dists_2_enemies) / max_dist  # 敌方算子所处位置的临近程度
    total_measures = 0.8 * enemy_uncertainties + 0.2 * proximities
    chosen_enemy = enemies_info[np.argmax(total_measures)]
    return chosen_enemy['obj_id']


def get_cm_search_target_2nd(obj_id, enemies, myAgent):
    """ 2020-7-6 用于群队级别
    确定巡飞弹的搜索对象
    :param enemies: 在分队级想定中就是myAgent.situations.enemies；在群队级想定中需要划分出部分
    """
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    cm = myAgent.get_bop(obj_id)
    cm_loc_ind = mapid_2_matind(cm['cur_hex'], map_size)
    # 确定其它巡飞弹的位置
    our_oprs = myAgent.agenda.rae_situation.our_oprs
    other_cms, other_cms_locs = [], []
    for opr in our_oprs:
        if opr['sub_type'] == BopType.CruiseMissile and opr['on_board'] == 0 and opr['obj_id'] != obj_id:
            other_cms.append(opr)
            other_cms_locs.append(opr['cur_hex'])
    enemies_info = []
    for enemy_info in enemies:
        if enemy_info['spread_flag']:
            enemy = enemy_info['operator']
            if enemy['type'] == BopType.Aircraft:  # 炮兵和空中算子不跟踪
                continue
            if enemy['sub_type'] == BopType.Artillery:
                continue
            enemies_info.append(enemy_info)
    if not enemies_info:
        return None
    n_enemies = len(enemies_info)
    enemy_locs = np.zeros(n_enemies, dtype=int)
    # === 敌方算子的位置不确定性 ===
    enemy_uncertainties = np.zeros(n_enemies, dtype=float)
    for m in range(n_enemies):
        enemy_info = enemies_info[m]
        enemy = enemy_info['operator']
        enemy_locs[m] = mapid_2_matind(enemy['cur_hex'], map_size)
        enemy_uncertainties[m] = 1 - np.max(enemy_info['pos_probs'])
    # === 敌方算子所处位置的临近程度 ===
    dists_2_enemies = dist_matrix[cm_loc_ind, enemy_locs]
    max_dist = np.max(dists_2_enemies)
    if max_dist < 1e-8:
        proximities = np.ones(n_enemies, dtype=float)
    else:
        proximities = (max_dist - dists_2_enemies) / max_dist
    # === 敌方算子到我方其它巡飞弹的疏远程度 ===
    if other_cms:
        other_cms_locs_ind = mapid_2_matind(np.array(other_cms_locs), map_size)
        dists_others_enemies = dist_matrix[other_cms_locs_ind.reshape(-1, 1), enemy_locs.reshape(1, -1)]
        min_dists_others_enemies = np.min(dists_others_enemies, axis=0)
        if np.max(min_dists_others_enemies) < 1e-8:
            remoteness = np.ones(n_enemies, dtype=float)
        else:
            remoteness = min_dists_others_enemies / np.max(min_dists_others_enemies)
    else:
        remoteness = np.ones(n_enemies, dtype=float)
    total_measures = 0.6 * enemy_uncertainties + 0.2 * proximities + 0.2 * remoteness
    chosen_enemy = enemies_info[np.argmax(total_measures)]
    return chosen_enemy['obj_id']


def get_drone_search_target(obj_id, enemies, myAgent):
    """ 2020-7-21 用于群队级别
    确定无人机的搜索对象
    :param enemies: 在分队级想定中就是myAgent.situations.enemies；在群队级想定中需要划分出部分
    """
    map_size = myAgent.get_map_size()
    dist_matrix = myAgent.my_map['dist_matrix']
    enemy_threat = myAgent.agenda.rae_situation.enemy_threat[ObservedType.LittleAircraft]
    drone = myAgent.get_bop(obj_id)
    drone_loc_ind = mapid_2_matind(drone['cur_hex'], map_size)
    # 确定其它drone的位置
    our_oprs = myAgent.get_our_oprs()
    another_drone, another_drone_loc = None, None
    for opr in our_oprs:
        if opr['sub_type'] == BopType.Drone and opr['obj_id'] != obj_id:
            another_drone = opr
            another_drone_loc = opr['cur_hex']
    if another_drone:
        another_drone_loc_ind = mapid_2_matind(another_drone_loc, map_size)
    enemies_info = []
    for enemy_info in enemies:
        if enemy_info['spread_flag']:
            enemy = enemy_info['operator']
            if enemy['type'] != BopType.Aircraft or (enemy['sub_type'] not in [BopType.Artillery, BopType.Man]):  # 炮兵、步兵和空中算子不跟踪
                enemies_info.append(enemy_info)
    if not enemies_info:
        return None
    n_enemies = len(enemies_info)
    enemy_locs = np.zeros(n_enemies, dtype=int)
    # === 敌方算子的位置不确定性和威胁程度 ===
    enemy_uncertainties = np.zeros(n_enemies, dtype=float)
    for m in range(n_enemies):
        enemy_info = enemies_info[m]
        enemy = enemy_info['operator']
        enemy_locs[m] = mapid_2_matind(enemy['cur_hex'], map_size)
        enemy_uncertainties[m] = 1 - np.max(enemy_info['pos_probs'])
    # === 敌方算子位置的威胁程度 ===
    threats = np.minimum(enemy_threat[enemy_locs], np.ones(n_enemies) * 10)
    safty = 1 - threats / 10
    # === 敌方算子所处位置的临近程度 ===
    dists_2_enemies = dist_matrix[drone_loc_ind, enemy_locs]
    max_dist = np.max(dists_2_enemies)
    if max_dist < 1e-8:
        proximities = np.ones(n_enemies, dtype=float)
    else:
        proximities = (max_dist - dists_2_enemies) / max_dist
    # === 敌方算子到我方其它巡飞弹的疏远程度 ===
    if another_drone:
        dist_another_enemies = dist_matrix[another_drone_loc_ind, enemy_locs.reshape(1, -1)]
        if np.max(dist_another_enemies) < 1e-8:
            remoteness = np.ones(n_enemies, dtype=float)
        else:
            remoteness = dist_another_enemies / np.max(dist_another_enemies)
    else:
        remoteness = np.ones(n_enemies, dtype=float)
    total_measures = 0.3 * enemy_uncertainties + 0.4 * safty + 0.15 * proximities + 0.15 * remoteness
    ind = np.argmax(total_measures)
    chosen_enemy = enemies_info[ind]
    return chosen_enemy['obj_id']


def get_cm_random_search_loc(myAgent):
    """2020-7-17 随机获得巡飞弹的搜索目标点（也用于无人机的搜索任务中）
    """
    map_size = myAgent.get_map_size()
    city_locs = [city['coord'] for city in myAgent.observation['cities']]
    candidate_locs = []
    for loc in city_locs:
        candidate_locs += get_range(loc, 5, map_size)
    candidate_locs = list(set(candidate_locs))
    return choice(candidate_locs)


def get_artillery_target(myAgent):
    """2020-6-25
    确定炮兵的目标
    """
    targets = {0: [], 1: [], 2: [], 3: []}  # 按级别区分的目标
    # 0：压制的步兵
    # 1：无掩蔽、无地形保护的步兵（有侦察单位)
    # 2：无掩蔽、无地形保护的步兵（无侦察单位)，其它步兵（有侦察单位），无掩蔽、无地形保护的车辆
    # 3： 其它步兵（无侦察单位），其它车辆
    # 确定可用的校射算子类型
    cm_flag, drone_flag, heli_flag = False, False, False
    our_oprs = myAgent.agenda.rae_situation.our_oprs
    for opr in our_oprs:
        if opr['sub_type'] == BopType.CruiseMissile and opr['on_board'] == 0:
            cm_flag = True
        if opr['sub_type'] == BopType.Drone:
            drone_flag = True
        if opr['sub_type'] == BopType.Helicopter:
            heli_flag = True
    # 区分目标等级
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        enemy = enemy_info['operators']
        ene_id = enemy['obj_id']
        ene_loc = enemy['cur_hex']
        if enemy['type'] == BopType.Aircraft:
            continue
        if enemy['armor'] == ArmorType.Composit:
            continue
        if np.max(enemy_info['pos_probs']) < 1 - 1e-8:
            continue
        if enemy['stop'] == 0:  # 算子没有处于静止状态
            continue
        if enemy['type'] == BopType.Infantry:
            if enemy['keep'] == 1:
                targets[0].append(ene_id)  # 最高等级是压制的步兵
            elif cm_flag:
                targets[1].append(ene_id)
            elif drone_flag:
                terrain = get_terrain_info(myAgent.map.get_map_data(), ene_loc)
                if enemy['move_state'] != MoveState.Shelter and terrain not in [TerrainType.Forest,
                                                                                TerrainType.Resident]:
                    targets[1].append(ene_id)
                elif enemy_info['remain_bullet_nums'][PortableMissile().type] <= 0:  # 没有导弹就没有对空威胁
                    targets[2].append(ene_id)
                else:
                    targets[3].append(ene_id)
            elif heli_flag:
                terrain = get_terrain_info(myAgent.map.get_map_data(), ene_loc)
                if enemy_info['remain_bullet_nums'][PortableMissile().type] <= 0:  # 没有导弹就没有对空威胁
                    if enemy['move_state'] != MoveState.Shelter and terrain not in [TerrainType.Forest,
                                                                                    TerrainType.Resident]:
                        targets[1].append(ene_id)
                    else:
                        targets[2].append(ene_id)
                else:
                    if enemy['move_state'] != MoveState.Shelter and terrain not in [TerrainType.Forest,
                                                                                    TerrainType.Resident]:
                        targets[2].append(ene_id)
                    else:
                        targets[3].append(ene_id)
            else:
                terrain = get_terrain_info(myAgent.map.get_map_data(), ene_loc)
                if enemy['move_state'] != MoveState.Shelter and terrain not in [TerrainType.Forest,
                                                                                TerrainType.Resident]:
                    targets[2].append(ene_id)
                else:
                    targets[3].append(ene_id)
        else:  # 车辆算子的目标
            terrain = get_terrain_info(myAgent.map.get_map_data(), ene_loc)
            if enemy['move_state'] != MoveState.Shelter and terrain not in [TerrainType.Forest,
                                                                            TerrainType.Resident]:
                targets[2].append(ene_id)
            else:
                targets[3].append(ene_id)
    return targets


def get_best_fire_action(obj_id, valid_fire_actions, myAgent):
    """在Fire的动作中输出最优动作"""
    obj = myAgent.get_bop(obj_id)
    max_att_level = 0
    best_fire_action = None
    for valid_action in valid_fire_actions:
        target_obj_id = valid_action['target_obj_id']
        att_level = valid_action['attack_level']
        tar_obj = myAgent.get_bop(target_obj_id)
        if tar_obj:
            if obj['sub_type'] == BopType.Helicopter:
                if myAgent.color == 1:
                    if tar_obj['sub_type'] == BopType.IFV:
                        att_level *= 2
                    elif tar_obj['sub_type'] == BopType.UCV:
                        att_level /= 3
                elif tar_obj['sub_type'] == BopType.Infantry:
                    att_level /= 2
            elif obj['type'] == BopType.Infantry:
                if tar_obj['type'] == BopType.Aircraft:
                    att_level *= 2
                elif tar_obj['sub_type'] == BopType.Infantry and tar_obj['keep'] == 1:
                    att_level *= 2
                elif tar_obj['sub_type'] == BopType.Infantry:
                    att_level /= 2
            elif obj['sub_type'] in [BopType.IFV, BopType.Tank]:
                if tar_obj['sub_type'] == BopType.Infantry and tar_obj['keep'] == 1:
                    att_level *= 2
                elif tar_obj['type'] == BopType.Infantry:
                    att_level /= 2
                elif tar_obj['sub_type'] == BopType.IFV:
                    att_level *= 1.5
            elif obj['sub_type'] == BopType.UCV:
                if tar_obj['sub_type'] == BopType.Infantry:
                    att_level *= 2
            if tar_obj['sub_type'] == BopType.Artillery:  # 从不打炮兵
                att_level = 0.01
            if att_level > max_att_level:
                max_att_level = att_level
                best_fire_action = valid_action
    return best_fire_action

def be_seen(obj_id, myAgent):
    """ 2020-6-6
    感知最低级别的威胁，是否被发现
    """
    obj = myAgent.get_bop(obj_id)
    if not obj:
        # print(f'current step {myAgent.agenda.rae_observation["time"]["cur_step"]}  self obj: {obj}')
        return False
    enemy_ids = obj['see_enemy_bop_ids']
    if not enemy_ids:
        # print(f'current step {myAgent.agenda.rae_observation["time"]["cur_step"]}  our obj_id: {obj_id}  enemy_ids: {enemy_ids}')
        return False
    # print(
    #     f'current step {myAgent.agenda.rae_observation["time"]["cur_step"]}  our obj_id: {obj_id}  enemy_ids: {enemy_ids}')
    # print([enemy_infor['obj_id'] for enemy_infor in myAgent.agenda.rae_situation.enemies])
    for ene_id in enemy_ids:
        enemy = myAgent.get_bop(ene_id)
        # print(f'current step: {myAgent.agenda.rae_observation["time"]["cur_step"]}  our obj_id: {obj_id}  ene_id: {ene_id} enemy_hex: {enemy["cur_hex"]} pos_probs: {np.max(myAgent.agenda.get_enemy_info(ene_id)["pos_probs"])}')
        if enemy:
            if can_see_obj(enemy, obj, myAgent.map):
                return True
    return False


def perceived_thread(obj_id, tolerance, myAgent):
    """ 2020-7-17
    感知威胁（预判时间为tolerance秒内是否有威胁），后期可以进一步根据数据分析进行智能化
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    obj = myAgent.get_bop(obj_id)
    cur_loc = obj['cur_hex']
    cur_loc_ind = mapid_2_matind(cur_loc, map_size)
    # == 找出算子被观察区域 ==
    # observed_area = get_opr_observed_range(obj, myAgent)
    # if observed_area.size == 0:
    #     return False
    # == 找出威胁等级目前大于4的区域 ==
    if obj['type'] == BopType.Vehicle:
        enemy_threat = myAgent.agenda.rae_situation.enemy_threat[0]
    elif obj['type'] == BopType.Infantry:
        enemy_threat = myAgent.agenda.rae_situation.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        enemy_threat = myAgent.agenda.rae_situation.enemy_threat[2]
    else:
        enemy_threat = myAgent.agenda.rae_situation.enemy_threat[3]
    threat_area = np.arange(n_hexes)[enemy_threat >= 4]
    if threat_area.size == 0:
        return False
    # == 计算observed_area和threat_area的距离 ==
    cost_matrix = myAgent.my_map['cost_matrix'][0]  # 假设敌方是车辆
    return np.any(cost_matrix[cur_loc_ind, threat_area.reshape(1, -1)] <= tolerance)


def confirm_defend_area(tar_hex, directions, radius, dist_matrix, map_size):
    """
    确定防守目标区域
    :param tar_hex: {int: map_id} 防守目标格
    :param directions: {list} 防守方向，支持传入int类型
    :param radius: {int} 防守半径
    :param dist_matrix: {narray} 距离矩阵
    :param map_size:
    :return: 防守目标区域，由mat_ind构造的向量
    """
    if isinstance(directions, int):
        directions = [directions]
    if radius <= 10:
        shift = 2
    else:
        shift = 4
    defend_area_1 = []
    for dir in directions:
        opposite_dir = (dir + 3) % 6
        delta_cube = Directions.Cube[opposite_dir].cub_multiply(shift)
        tar_hex_shift = hexoffset_2_mapid(mapid_2_hexoffset(tar_hex).cvt_2_cube().cube_add(delta_cube).cvt_2_offset())
        defend_area_1 += get_sector(tar_hex_shift, radius + shift, dir, map_size)
    defend_area_1 = np.array(list(set(defend_area_1)))  # 消除重复六角格，并转换成array格式
    # 删除防守区域中距离tar_hex六角格 4格 以内的所有六角格
    dist_vec = dist_matrix[mapid_2_matind(tar_hex, map_size)]
    defend_area_1_ind = mapid_2_matind(defend_area_1, map_size).astype('int64')
    defend_area_2_ind = defend_area_1_ind[dist_vec[defend_area_1_ind] > shift]
    return defend_area_2_ind


def infer_cm_path(cur_hex, area):
    """ 2020-5-7
    为巡飞弹规划出area区域中的巡逻路线，起始格和终止格是同一个格子
    """
    # 计算出半径为2的圆在area中的一个全覆盖，得到所有圆的圆心
    waypts = get_cover_circles(area, 2)
    # 找出距离cur_hex最近的一个圆
    start_pt = min(waypts, key=lambda x: cal_distance(x, cur_hex))
    # 将所有圆心按照启发式TSP问题连成一个环路
    circle = [start_pt]
    waypts.remove(start_pt)
    curr_pt = start_pt
    while waypts:
        next_pt = min(waypts, key=lambda x: cal_distance(x, curr_pt))
        circle.append(next_pt)
        waypts.remove(next_pt)
        curr_pt = next_pt
    circle.append(start_pt)
    # 用直线填充环路
    path_circ = [start_pt]
    circ_num = len(circle)
    for m in range(1, circ_num):
        hex_s, hex_t = circle[m - 1], circle[m]
        line = get_line(hex_s, hex_t)
        path_circ = path_circ + line
    return path_circ


def infer_cm_fire_path(cm_id, ene_id, myAgent):
    """ 2020-5-1
    根据敌方算子的历史信息，计算出巡飞弹的追击路线：起始格是当前格的下一格
    """
    ene_opr = myAgent.agenda.get_enemy_info(ene_id)['operator']
    ene_step = myAgent.agenda.get_enemy_info(ene_id)['step']
    ene_speed = ene_opr['basic_speed'] / 3.6
    map_size = myAgent.get_map_size()
    cur_step = myAgent.observation['time']['cur_step']
    cur_hex = myAgent.get_bop(cm_id)['cur_hex']
    cm_speed = 100 / 3.6
    distance = cal_distance(cur_hex, ene_opr['cur_hex']) * 200
    max_escape_len = ene_speed * (cur_step - ene_step)  # 丢失视野的时段，算子最大的逃离路程（米）
    hunt_radius = int((ene_speed * distance + cm_speed * max_escape_len) / (cm_speed - ene_speed) / 200)
    max_radius = int(myAgent.get_bop(cm_id)['alive_remain_time'] * cm_speed / 200)
    if hunt_radius <= max_radius:
        hunt_area = get_range(ene_opr['cur_hex'], hunt_radius, map_size)
    else:
        hunt_area = get_range(ene_opr['cur_hex'], max_radius, map_size)
    patrol_path = infer_cm_path(cur_hex, hunt_area)
    arrive_path = get_line(cur_hex, patrol_path[0])
    hunt_path = arrive_path[:-1] + patrol_path
    return hunt_path


# def infer_fire_loc(state, opr, area):


# def infer_off_loc(ifv_id, tar_loc, myAgent):
#     """ 2020-5-2
#     为步兵推理下车点
#     """
#     # 确定方向
#     enemies_id = confirm_enemies(myAgent, 3)
#     dir_of_enemies = []
#     for ene_id in enemies_id:
#         ene_loc = myAgent.agenda.get_enemy_info(ene_id)['operator']['cur_hex']
#         dir_to_tar_loc = get_direction(tar_loc, ene_loc)
#         if dir_to_tar_loc not in dir_of_enemies:
#             dir_of_enemies.append(dir_to_tar_loc)
#     ifv_obj = myAgent.agenda.get_bop(ifv_id)
#     direction = get_direction(tar_loc, ifv_obj['cur_hex'])
#     half_sphere = [(direction - 1) % 6, direction, (direction + 1) % 6]  # 目标点tar_loc的我方一侧
#     if 0 < len(dir_of_enemies) <= 3 and dir_of_enemies:
#         directions_off = [(d + 3) % 6 for d in dir_of_enemies]  # 敌人的反方向就是下车的方向
#     else:  # 敌人过于散布时，按我方位置确定
#         directions_off = half_sphere
#     # 初步确定可能的下车点，3格以内的half_sphere一侧
#     directions_hide = [(d + 3) % 6 for d in directions_off]  # 需要隐蔽的方向是下车的反方向
#     off_area = {}
#     for d in directions_off:
#         for r in range(1, 4):  # 范围在3个以内
#             arc = get_arc(tar_loc, r, d, myAgent.get_map_size())
#             for hex in arc:
#                 # 计算最大的速度乘子，便于机动的度量
#                 neighbors = myAgent.map.get_neighbors(hex)
#                 max_speed_multiplier = 0
#                 for nei in neighbors:
#                     if get_direction(hex, nei) in half_sphere:
#                         cur_multiplier = 1. / calc_speed_multiplier(nei, hex, 2, myAgent.my_map['cost_data'])
#                         if max_speed_multiplier < cur_multiplier:
#                             max_speed_multiplier = cur_multiplier
#                 # 计算隐蔽率，隐蔽性的度量
#                 hide_ratio = calc_hide_ratio(2, hex, directions_hide, myAgent)
#                 off_area[hex] = (r / 3., max_speed_multiplier, hide_ratio)  # (距离，机动，隐蔽)
#     # 一个好的下车点：离tar_loc越远越好(0.1)、便于机动(0.2)、不容易被发现(0.7)
#     off_loc = max(off_area, key=lambda x: off_area[x][0] * 0.1 + off_area[x][1] * 0.2 + off_area[x][2] * 0.7)
#     return off_loc


def infer_off_loc(ifv_id, tar_loc, myAgent):
    """ 2020-6-20
    为步兵推理下车点
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    dist_matrix = myAgent.my_map['dist_matrix']
    cost_matrix = myAgent.my_map['cost_matrix']
    ifv = myAgent.get_bop(ifv_id)
    ifv_loc_ind = mapid_2_matind(ifv['cur_hex'], map_size)
    city_flag = True if myAgent.get_city(tar_loc) else False
    tar_loc_ind = mapid_2_matind(tar_loc, map_size)
    if city_flag:
        off_area_bool = (dist_matrix[tar_loc_ind] <= 3)
    else:
        off_area_bool = (dist_matrix[tar_loc_ind] <= 2)
    off_area = np.arange(n_hexes)[off_area_bool]
    off_area_size = off_area.size
    # 1. 计算当前的安全程度
    threats = myAgent.agenda.rae_situation.enemy_threat[0][off_area]
    max_threats, min_threats = np.max(threats), np.min(threats)
    if max_threats - min_threats < 1e-8:
        safty_norm = np.ones(off_area_size, dtype=float)
    else:
        safty_norm = (max_threats - threats) / (max_threats - min_threats)
    # 2. 计算易达程度
    move_costs = cost_matrix[0][ifv_loc_ind, off_area.reshape(1, -1)]
    max_costs, min_costs = np.max(move_costs), np.min(move_costs)
    if max_costs - min_costs < 1e-8:
        accessibilites = np.ones(off_area_size, dtype=float)
    else:
        accessibilites = (max_costs - move_costs) / (max_costs - min_costs)
    # 3. 计算到目标点的临近程度
    tar_dists = cost_matrix[1][tar_loc_ind, off_area.reshape(1, -1)]
    max_dist, min_dist = np.max(tar_dists), np.min(tar_dists)
    if max_dist - min_dist < 1e-8:
        proximities = np.ones(off_area_size, dtype=float)
    else:
        proximities = (max_dist - tar_dists) / (max_dist - min_dist)
    # 综合评估
    total_measures = 0.5 * safty_norm + 0.4 * accessibilites + 0.1 * proximities
    off_loc = matind_2_mapid(off_area[np.argmax(total_measures)], map_size)
    return off_loc


def infer_move_shoot_locs(obj_id, tar_id, myAgent):
    """ 2020-6-12
    推理行进间射击的射击点位和下一步机动点位
    :return: 输出是下一步的机动点位
    """
    if tar_id:
        if myAgent.agenda.get_enemy_info(tar_id):
            enemies = [myAgent.agenda.get_enemy_info(tar_id)]
        else:
            return None  # 没有必要执行行进间射击动作
    else:
        enemies = myAgent.agenda.rae_situation.enemies
        if not enemies:
            return None  # 没有必要执行行进间射击动作
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    n_hexes = int(map_size['row'] * map_size['col'])
    cost_matrix = myAgent.my_map['cost_matrix'][0]
    enemy_threat = myAgent.agenda.rae_situation.enemy_threat[0]
    cur_hex = obj['cur_hex']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    unit_time = 200. / (obj['basic_speed'] / 3.6)
    # ==== 计算火力圈的攻击效能 ====
    fire_circle_bool = (cost_matrix[cur_hex_ind] <= 4 * unit_time)
    fire_circle = np.arange(n_hexes)[fire_circle_bool]
    fire_circle_vol = fire_circle.size
    attackabilities_circle = np.zeros(fire_circle.size, dtype=float)
    for enemy_info in enemies:
        pos_probs = enemy_info['pos_probs']
        tar_area_ind = np.arange(n_hexes)[pos_probs > 0]
        tar_type = enemy_info['operator']['type']
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              fire_circle, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                              special_shoot='move_shoot').astype(float)
        if tar_type == BopType.Infantry:
            attack_levels /= 2.
        elif tar_type == BopType.Vehicle:
            attack_levels *= 1.5
        attackabilities_this = attack_levels @ pos_probs[tar_area_ind]
        attackabilities_circle = np.maximum(attackabilities_circle, attackabilities_this)
    if np.max(attackabilities_circle) < 1e-8:  # 4个范围内，没有有效的射击点位，保持不动，表示等待
        return cur_hex
    # ==== 计算下一点位的评估指标：攻击性和安全性 ====
    time_window = math.ceil(obj['weapon_cool_time'] / unit_time) * unit_time
    candidate_locs = np.array(get_range(cur_hex, 1, map_size))  # 下一点位的候选区域
    candidate_locs_ind = mapid_2_matind(candidate_locs, map_size)
    candidate_locs_size = candidate_locs.size
    attackabilities_candid = np.zeros(candidate_locs_size, dtype=float)  # 初始化攻击性
    # 1. 攻击性评估
    for m in range(candidate_locs_size):
        loc_ind = candidate_locs_ind[m]
        move_time_left = max(time_window - cost_matrix[cur_hex_ind, loc_ind], 0.)
        move_cost_fire_circle = np.maximum(cost_matrix[loc_ind][fire_circle_bool] - move_time_left,
                                           np.zeros(fire_circle_vol))
        attack_weights = move_cost_fire_circle / unit_time + 1.
        attackabilities_candid[m] = np.max(attackabilities_circle / (attack_weights ** 2))
    if np.max(attackabilities_candid) < 1e-8:
        attackabilities_norm = attackabilities_candid
    else:
        attackabilities_norm = attackabilities_candid / np.max(attackabilities_candid)
    # 2. 安全性评估
    # - 安全圈与火力圈相同，即safe_circle = fire_circle
    threat_base = enemy_threat[fire_circle]  # 威胁程度基数
    for m in range(fire_circle_vol):
        loc = matind_2_mapid(fire_circle[m], map_size)
        if loc == cur_hex:
            threat_base[m] *= 1.1  # “静止”的惩罚
        # neighbors = myAgent.map.get_neighbors(loc) + [loc]
        other_opr_locs = [opr['cur_hex'] for opr in myAgent.get_our_oprs() if opr['obj_id'] != obj_id]
        if loc in other_opr_locs:
            threat_base[m] *= 1.1  # “堆叠”的惩罚
    # - 确定安全圈中六角格的相邻性
    dissimilarities = cost_matrix[candidate_locs_ind.reshape(-1, 1), fire_circle.reshape(1, -1)]
    dissimilarities = np.maximum(dissimilarities / unit_time, np.ones(dissimilarities.shape, dtype=float))
    if abs(np.max(threat_base) - np.min(threat_base)) < 1e-8:
        circle_safty = np.ones(fire_circle_vol, dtype=float)
    else:
        circle_safty = (np.max(threat_base) - threat_base) / (np.max(threat_base) - np.min(threat_base))
    safty_norm = np.max(circle_safty.reshape(1, -1) / dissimilarities, axis=1)
    # 3. 综合评估
    total_measures = 0.8 * attackabilities_norm + 0.2 * safty_norm
    next_loc = candidate_locs[np.argmax(total_measures)]
    return next_loc


def infer_move_shoot_path(obj_id, myAgent):
    """ 2020-5-4
    推理行进间射击的射击点位和机动路径
    """
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    n_hex = int(map_size['row'] * map_size['col'])
    if is_moving(obj):
        cur_hex = obj['move_path'][0]
        time_next_left = (1. - obj['cur_pos']) * 200. / (obj['basic_speed'] / 3.6)
        basic_time = ((obj['weapon_cool_time'] - time_next_left) // 20 + 1) * 20
    else:
        cur_hex = obj['cur_hex']
        basic_time = (obj['weapon_cool_time'] // 20 + 1) * 20
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    cost_matrix = myAgent.my_map['cost_matrix'][0]
    dist_matrix = myAgent.my_map['dist_matrix']
    see_matrix = myAgent.my_map['see_matrix'][0]
    # 确定隐蔽区域
    hide_area_inds = np.arange(n_hex)[cost_matrix[cur_hex_ind] <= basic_time + 80]
    tar_locs = np.arange(n_hex)[dist_matrix[cur_hex_ind] <= 25]
    enemy_loc_probs_full = np.zeros(n_hex)
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        enemy_loc_probs_full += predict_enemy_locs(myAgent, enemy_info)
    enemy_loc_probs = enemy_loc_probs_full[tar_locs]
    hide_ratio = 1 - calc_unhidden_ratio(obj['type'], hide_area_inds, tar_locs, see_matrix, dist_matrix,
                                         tar_loc_probs=enemy_loc_probs)  # 隐蔽区域的隐蔽性
    hide_ratio_full = np.zeros(n_hex)
    hide_ratio_full[hide_area_inds] = hide_ratio
    # 确定开火区域
    trigger_area_inds = np.arange(n_hex)[
        (cost_matrix[cur_hex_ind] <= basic_time + 20) & (cost_matrix[cur_hex_ind] >= basic_time)]
    n_trigger_locs = trigger_area_inds.size
    # 1.计算开火点位的攻击性
    n_enemy = len(myAgent.agenda.rae_situation.enemies)
    predict_damages = np.zeros((n_trigger_locs, n_enemy))
    for m in range(n_enemy):
        enemy_info = myAgent.agenda.rae_situation.enemies[m]
        enemy = enemy_info['operator']
        enemy_loc_probs_full = predict_enemy_locs(myAgent, enemy_info)
        enemy_locs = np.arange(n_hex)[enemy_loc_probs_full > 0]
        enemy_loc_probs = enemy_loc_probs_full[enemy_locs]
        if enemy_locs.size == 0:
            print("enemy['cur_hex']: ", enemy['cur_hex'])
            print("enemy_locs.shape: ", enemy_locs.shape)
            print("np.max(enemy_loc_probs_full): ", np.max(enemy_loc_probs_full))
        damages_enemy = calc_damage_common(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                           trigger_area_inds, enemy['type'], enemy_locs, obj['type'], obj['blood'],
                                           enemy['armor'], enemy['keep'], 'move_shoot')
        predict_damages[:, m] = np.sum(damages_enemy * np.tile(enemy_loc_probs, (n_trigger_locs, 1)), axis=1)
    if n_enemy == 0:
        attackability = np.ones(n_trigger_locs)
    elif n_enemy == 1:
        attackability = predict_damages[:, 0]
    else:
        attackability = np.mean(np.sort(predict_damages)[:, -2:], axis=1)
    if np.max(attackability) == np.min(attackability):
        attackability_norm = np.ones(n_trigger_locs)
    else:
        attackability_norm = (attackability - np.min(attackability)) / (np.max(attackability) - np.min(attackability))
    # 2.计算开火点位的隐蔽性
    candidate_hide_locs = cost_matrix[trigger_area_inds] * (
            (cost_matrix[trigger_area_inds] >= 40) & (cost_matrix[trigger_area_inds] <= 60) + 0)
    candidate_hide_locs[candidate_hide_locs > 0] = - candidate_hide_locs[
        candidate_hide_locs > 0] / 2000 + (1 + basic_time / 2000.)  # 40: 1, 60: 0.99
    hide_loc_inds = np.argmax(candidate_hide_locs * np.tile(hide_ratio_full, (n_trigger_locs, 1)), axis=1)
    invisibility = np.tile(hide_ratio_full, (n_trigger_locs, 1))[np.arange(n_trigger_locs), hide_loc_inds]
    if np.max(invisibility) == np.min(invisibility):
        invisibility_norm = np.ones(n_trigger_locs)
    else:
        invisibility_norm = (invisibility - np.min(invisibility)) / (np.max(invisibility) - np.min(invisibility))
    # 3.机动耗时越短越好
    move_cost = cost_matrix[cur_hex_ind, trigger_area_inds] + cost_matrix[trigger_area_inds, hide_loc_inds]
    if np.max(move_cost) == np.min(move_cost):
        move_cost_norm = np.ones(n_trigger_locs)
    else:
        move_cost_norm = (np.max(move_cost) - move_cost) / (np.max(move_cost) - np.min(move_cost))
    # 4.离敌方越近越好
    avg_dist_enemies = np.sum(cost_matrix[trigger_area_inds] * np.tile(enemy_loc_probs_full, (n_trigger_locs, 1)),
                              axis=1)
    if np.max(avg_dist_enemies) == np.min(avg_dist_enemies):
        avg_dist_enemies_norm = np.ones(n_trigger_locs)
    else:
        avg_dist_enemies_norm = (np.max(avg_dist_enemies) - avg_dist_enemies) / (
                np.max(avg_dist_enemies) - np.min(avg_dist_enemies))
    # 综合计算
    total_measure = 0.4 * attackability_norm + 0.4 * invisibility_norm + 0.19 * move_cost_norm + 0.01 * avg_dist_enemies_norm
    best_fire_loc_ind = trigger_area_inds[np.argmax(total_measure)]
    best_hide_loc_ind = hide_loc_inds[trigger_area_inds == best_fire_loc_ind][0]
    best_fire_loc = matind_2_mapid(best_fire_loc_ind, map_size)
    best_hide_loc = matind_2_mapid(best_hide_loc_ind, map_size)
    # 计算机动路径，并输出
    path = myAgent.map.gen_move_route(cur_hex, best_fire_loc, 0) + myAgent.map.gen_move_route(best_fire_loc,
                                                                                              best_hide_loc, 0)
    return path, best_fire_loc


def infer_fire_loc(obj_id, tar_area, tar_type, myAgent, move_shoot=False):
    """ 2020-6-8
    根据敌方所在位置推理算子的攻击点
    主要考虑的因素：对目标区域的火力覆盖水平、攻击点的安全性、出发地临近程度
    :param tar_area: 2种取值，1）list，由六角格（map_id）构成，各个格子概率相同；
                              2）np.array，n_hexes长度的一维数组（mat_ind），表示对应六角格的概率（pos_probs）
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    if isinstance(tar_area, list):
        tar_area_ind = mapid_2_matind(np.array(tar_area), map_size).astype(int)
        pos_probs = np.ones(len(tar_area)) / len(tar_area)
    else:
        tar_area_ind = np.arange(n_hexes)[tar_area > 0]
        pos_probs = tar_area[tar_area > 0]
    obj = myAgent.get_bop(obj_id)
    # obj = obj_id  # 调试用
    cur_hex = obj['cur_hex']
    obj_type = obj['type']
    obj_speed = obj['basic_speed']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:  # 空中算子
        cost_matrix = dist_matrix * (200. / obj_speed * 3.6)
    # 确定推理模式
    precise_infer, rough_infer = range(2)
    if obj_type == BopType.Vehicle:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[0]
        neighbors_bool = (cost_matrix[cur_hex_ind] <= 2 * (200. / obj_speed * 3.6))
        if np.max(threat_situation[neighbors_bool]) > 0:
            infer_mod = precise_infer  # 使用当前threat_situation确定安全性
        else:
            infer_mod = rough_infer  # 根据地形确定安全性
    elif obj_type == BopType.Infantry:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[1]
        neighbors_bool = (cost_matrix[cur_hex_ind] <= 1 * (200. / obj_speed * 3.6))
        if np.max(threat_situation[neighbors_bool]) > 0:
            infer_mod = precise_infer  # 使用当前threat_situation确定安全性
        else:
            infer_mod = rough_infer  # 根据地形确定安全性
    elif obj['sub_type'] == BopType.Helicopter:  # 空中算子一定是Precise_infer
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[2]
        infer_mod = precise_infer
    else:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[3]
        infer_mod = precise_infer
    # 初步筛选出可能的攻击点位
    special_shoot = 'move_shoot' if move_shoot else None
    shoot_radius = get_shoot_radius(obj['carry_weapon_ids'], obj['remain_bullet_nums'], tar_type, special_shoot)
    possible_shoot_area_bool = np.any(dist_matrix[tar_area_ind] <= shoot_radius, axis=0)
    possible_shoot_area = np.arange(n_hexes)[possible_shoot_area_bool]
    # 1. 计算攻击性
    if move_shoot:  # 行进间射击的情况
        fire_area_bool = np.any(cost_matrix[possible_shoot_area_bool] <= 40, axis=0)
        fire_area = np.arange(n_hexes)[fire_area_bool]
        fire_area_att_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                                     fire_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                                     special_shoot='move_shoot')
        fire_area_attackabilities = fire_area_att_levels @ pos_probs
        fire_area_attackabilities_full = np.zeros(n_hexes)
        fire_area_attackabilities_full[fire_area] = fire_area_attackabilities
        attackabilities = np.max(
            ((cost_matrix[possible_shoot_area] <= 40) + 0) * np.tile(fire_area_attackabilities_full,
                                                                     (possible_shoot_area.size, 1)), axis=1)
    else:  # 普通射击的情况
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              possible_shoot_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                              special_shoot=None)
        attackabilities = attack_levels @ pos_probs
    max_attack, min_attack = np.max(attackabilities), np.min(attackabilities)
    if max_attack - min_attack < 1e-8:
        attackabilities_norm = np.ones(possible_shoot_area.size)
    else:
        attackabilities_norm = (attackabilities - min_attack) / (max_attack - min_attack)
    # 2. 计算安全性（使用威胁态势进行计算）
    if infer_mod == precise_infer:
        threat = threat_situation[possible_shoot_area]
        max_threat, min_threat = np.max(threat), np.min(threat)
        safty_norm = (max_threat - threat) / (max_threat - min_threat)
    else:
        if move_shoot:  # 行进间射击的情况，用六角格的隐蔽性代替安全性
            see_matrix = myAgent.my_map['see_matrix'][0]
            safty_norm = 1 - calc_unhidden_ratio(obj_type, possible_shoot_area, tar_area_ind, see_matrix, dist_matrix,
                                                 distance=None, tar_loc_probs=pos_probs)
        else:  # 普通射击的情况，只考虑六角格地形的安全性
            if obj_type == BopType.Infantry:
                terrain_rect = np.array(TerrainMeanRect.Soldier)
            else:
                terrain_rect = np.array(TerrainMeanRect.Vehicle[obj['armor']])
            terrain_rect_norm = (np.max(terrain_rect) - terrain_rect) / (np.max(terrain_rect) - np.min(terrain_rect))
            safty_norm = terrain_rect_norm[myAgent.my_map['terrain_vec'][possible_shoot_area]]
    # 3. 计算出发地临近程度
    move_costs = cost_matrix[cur_hex_ind][possible_shoot_area]
    max_move_costs, min_move_costs = np.max(move_costs), np.min(move_costs)
    if max_move_costs - min_move_costs < 1e-8:
        proximities = np.ones(possible_shoot_area.size)
    else:
        proximities = (max_move_costs - move_costs) / (max_move_costs - min_move_costs)
    # 综合计算
    total_measure = 0.3 * attackabilities_norm + 0.1 * safty_norm + 0.6 * proximities
    best_fire_loc_ind = possible_shoot_area[np.argmax(total_measure)]
    best_fire_loc = matind_2_mapid(best_fire_loc_ind, map_size)
    return best_fire_loc


def infer_targeted_fire_loc(obj_id, tar_id, myAgent, move_shoot=False):
    """ 2020-6-16
    针对敌方算子tar_id推理算子obj_id的攻击点
    主要考虑的因素：对目标区域的火力覆盖水平、攻击点的安全性、出发地临近程度
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    enemy_info = myAgent.agenda.get_enemy_info(tar_id)
    if not enemy_info:
        return None  # 表示敌算子已死亡
    if not enemy_info['spread_flag']:
        return None
    enemy = enemy_info['operator']
    tar_type = enemy['type']
    pos_probs_full = enemy_info['pos_probs']
    tar_area_ind = np.arange(n_hexes)[pos_probs_full > 0]
    pos_probs = pos_probs_full[pos_probs_full > 0]
    obj = myAgent.get_bop(obj_id)
    cur_hex = obj['cur_hex']
    obj_type = obj['type']
    obj_speed = obj['basic_speed']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:  # 空中算子
        cost_matrix = dist_matrix * (200. / obj_speed * 3.6)
    # 选择威胁态势
    if obj_type == BopType.Vehicle:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[0]
    elif obj_type == BopType.Infantry:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[2]
    else:
        threat_situation = myAgent.agenda.rae_situation.enemy_threat[3]
    # 初步筛选出可能的攻击点位
    special_shoot = 'move_shoot' if move_shoot else None
    possible_shoot_area = get_enemy_shooted_range(enemy, obj, myAgent, special_shoot)
    # 1. 计算攻击性
    attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                          possible_shoot_area, tar_type, tar_area_ind, obj['type'], obj['blood'],
                                          special_shoot=None)
    attackabilities = attack_levels @ pos_probs
    max_attack, min_attack = np.max(attackabilities), np.min(attackabilities)
    if max_attack - min_attack < 1e-8:
        attackabilities_norm = np.ones(possible_shoot_area.size)
    else:
        attackabilities_norm = (attackabilities - min_attack) / (max_attack - min_attack)
    # 2. 计算安全性（使用威胁态势进行计算）
    threat = threat_situation[possible_shoot_area]
    max_threat, min_threat = np.max(threat), np.min(threat)
    safty_norm = (max_threat - threat) / (max_threat - min_threat)
    # 3. 计算出发地临近程度
    move_costs = cost_matrix[cur_hex_ind][possible_shoot_area]
    max_move_costs, min_move_costs = np.max(move_costs), np.min(move_costs)
    if max_move_costs - min_move_costs < 1e-8:
        proximities = np.ones(possible_shoot_area.size)
    else:
        proximities = (max_move_costs - move_costs) / (max_move_costs - min_move_costs)
    # 综合计算
    total_measure = 0.7 * attackabilities_norm + 0. * safty_norm + 0.3 * proximities
    best_fire_loc_ind = possible_shoot_area[np.argmax(total_measure)]
    best_fire_loc = matind_2_mapid(best_fire_loc_ind, map_size)
    return best_fire_loc


def infer_safe_loc(obj, area_radius, myAgent):
    """ 2020-7-19
    为算子obj在区域area中推理出最为安全的格子
    :param area_radius: 如果int，表示安全区域的半径；如果list，表示安全区域的六角格列表
    """
    cur_hex = obj['cur_hex']
    if obj['type'] == 1:  # 人员
        if isinstance(area_radius, int):
            area = get_range(cur_hex, area_radius, myAgent.get_map_size())
        else:
            area = area_radius
        forest_area = []
        resident_area = []
        other_area = []
        for hex in area:
            terrain = get_terrain_info(myAgent.map.get_map_data(), hex)
            if terrain == 1:  # 优先选择丛林地形
                forest_area.append(hex)
            elif terrain == 2:  # 其次选择居民地地形
                resident_area.append(hex)
            else:
                other_area.append(hex)
        if forest_area:
            return get_closest_hex(cur_hex, forest_area)
        elif resident_area:
            return get_closest_hex(cur_hex, resident_area)
        else:  # 普通地形主要考虑隐蔽性
            best_hide_ratio = 0
            best_hex = area[0]
            for hex in area:
                h_r = calc_hide_ratio(obj_type=1, cur_hex=hex, directions=list(range(6)),
                                      myAgent=myAgent)
                if best_hide_ratio < h_r:
                    best_hide_ratio = h_r
                    best_hex = hex
            return best_hex
    elif obj['type'] == BopType.Vehicle:  # 车辆
        map_size = myAgent.get_map_size()
        n_hexes = int(map_size['row'] * map_size['col'])
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        cur_hex_ind = mapid_2_matind(cur_hex, map_size)
        unit_radius = 200. / (obj['basic_speed'] / 3.6)
        if isinstance(area_radius, int):
            area_ind = np.arange(n_hexes)[cost_matrix[cur_hex_ind] <= area_radius * unit_radius]
        else:
            area_ind = mapid_2_matind(np.array(area_radius), map_size)
        # 计算安全性
        threat_area_bool = (myAgent.agenda.rae_situation.enemy_threat[0] >= 4)
        threat_area = np.arange(n_hexes)[threat_area_bool]
        saft_distances = cost_matrix[area_ind.reshape(-1, 1), threat_area.reshape(1, -1)]
        min_saft_distances = np.min(saft_distances, axis=1)
        dist_threshold = 5 * unit_radius  # 这个距离以上的安全性和这个距离的安全性设置相同
        min_saft_distances[min_saft_distances > dist_threshold] = dist_threshold
        safties_norm = min_saft_distances / dist_threshold
        # 计算临近性
        costs = cost_matrix[cur_hex_ind, area_ind.reshape(1, -1)]
        max_cost = np.max(costs)
        if max_cost < 1e-8:
            proximities_norm = np.ones(area_ind.size)
        else:
            proximities_norm = 1 - costs / max_cost
        # 综合评估
        total_measures = 0.7 * safties_norm + 0.3 * proximities_norm
        ind = np.argmax(total_measures)
        best_loc_ind = area_ind[ind]
        best_loc = matind_2_mapid(best_loc_ind, map_size)
        return best_loc
    else:  # 空中算子
        map_size = myAgent.get_map_size()
        n_hexes = int(map_size['row'] * map_size['col'])
        dist_matrix = myAgent.my_map['dist_matrix']
        cur_hex_ind = mapid_2_matind(cur_hex, map_size)
        # unit_radius = 200. / (obj['basic_speed'] / 3.6)
        if isinstance(area_radius, int):
            area_ind = np.arange(n_hexes)[dist_matrix[cur_hex_ind] <= area_radius]
        else:
            area_ind = mapid_2_matind(np.array(area_radius), map_size)
        # 计算安全性
        if obj['sub_type'] == BopType.Helicopter:
            threat_area_bool = (myAgent.agenda.rae_situation.enemy_threat[2] >= 3)
        else:
            threat_area_bool = (myAgent.agenda.rae_situation.enemy_threat[3] >= 3)
        threat_area = np.arange(n_hexes)[threat_area_bool]
        if threat_area.size <= 0:
            safties_norm = np.ones(area_ind.size)
        else:
            saft_distances = dist_matrix[area_ind.reshape(-1, 1), threat_area.reshape(1, -1)]
            min_saft_distances = np.min(saft_distances, axis=1)
            dist_threshold = 20  # 这个距离以上的安全性和这个距离的安全性设置相同
            min_saft_distances[min_saft_distances > dist_threshold] = dist_threshold
            safties_norm = min_saft_distances / dist_threshold
        # 计算临近性
        costs = dist_matrix[cur_hex_ind, area_ind.reshape(1, -1)]
        max_cost = np.max(costs)
        if max_cost < 1e-8:
            proximities_norm = np.ones(area_ind.size)
        else:
            proximities_norm = 1 - costs / max_cost
        # 综合评估
        total_measures = 0.9 * safties_norm + 0.1 * proximities_norm
        ind = np.argmax(total_measures)
        best_loc_ind = area_ind[ind]
        best_loc = matind_2_mapid(best_loc_ind, map_size)
        return best_loc


def infer_defend_loc(obj, tar_hex, directions, myAgent):
    """ 2020-5-24
    为算子obj针对方向directions进行防守点位的确定
    obj的类型只可能是“车辆”
    """
    dist_matrix = myAgent.my_map['dist_matrix']
    cost_matrix = myAgent.my_map['cost_matrix'][0]
    map_size = myAgent.get_map_size()
    n_hex = int(map_size['row'] * map_size['col'])
    cur_hex_ind = mapid_2_matind(obj['cur_hex'], map_size)
    if obj['type'] == BopType.Aircraft:
        see_matrix = myAgent.my_map['see_matrix'][1]
    else:
        see_matrix = myAgent.my_map['see_matrix'][0]
    # 确定防守目标区域
    thread_area_inds = confirm_defend_area(tar_hex, directions, 10, dist_matrix, map_size)
    # 确定防守点范围
    if obj['type'] == BopType.Infantry:
        radius = 1  # 根据机动耗时，选择防守点范围
    elif obj['type'] == BopType.Vehicle:
        radius = 10
    elif obj['type'] == BopType.Aircraft:
        radius = 20
    else:
        raise Exception('目标算子类型输入错误！')
    hex_inds = np.arange(n_hex)
    tar_hex_ind = mapid_2_matind(tar_hex, map_size)
    defend_locs_inds = hex_inds[dist_matrix[tar_hex_ind] <= radius]  # 防守点范围
    # 区分“坦克”和“非坦克”2中情况
    if obj['sub_type'] != BopType.Tank:
        # 1.计算攻击性
        attack_levels = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                              defend_locs_inds, BopType.Vehicle, thread_area_inds, obj['type'],
                                              obj['blood'], special_shoot=None)
        attackabilities = np.mean(attack_levels, axis=1)
        max_attack, min_attack = np.max(attackabilities), np.min(attackabilities)
        if max_attack - min_attack < 1e-8:
            attackabilities_norm = np.ones(defend_locs_inds.size)
        else:
            attackabilities_norm = (attackabilities - min_attack) / (max_attack - min_attack)
        # 2.计算安全性
        terrain_rect = np.array(TerrainMeanRect.Vehicle[obj['armor']])
        terrain_rect_norm = (np.max(terrain_rect) - terrain_rect) / (np.max(terrain_rect) - np.min(terrain_rect))
        safty_norm = terrain_rect_norm[myAgent.my_map['terrain_vec'][defend_locs_inds]]
        # 3.计算临近性：到当前位置的距离
        costs = cost_matrix[cur_hex_ind, defend_locs_inds.reshape(1, -1)]
        costs[costs > 0] += 40.  # 尽量待在原地
        max_cost = np.max(costs)
        if max_cost < 1e-8:
            proximity = np.ones(defend_locs_inds.size)
        else:
            proximity = 1 - costs / max_cost
        # 综合计算
        total_measure = 0.4 * attackabilities_norm + 0.4 * safty_norm + 0.2 * proximity
    else:  # 坦克的情况（行进间射击）
        # 1.计算攻击性
        cost_matrix = myAgent.my_map['cost_matrix'][0]
        attack_locs_each = (cost_matrix[defend_locs_inds] >= 40) & (cost_matrix[defend_locs_inds] <= 60)
        attack_loc_area = hex_inds[np.any(attack_locs_each, axis=0)]  # 攻击点位构成的区域
        attack_radius = 0
        for wp_id in obj['carry_weapon_ids']:
            weapon = get_weapon(wp_id)
            if obj['remain_bullet_nums'][weapon.type] > 0 and weapon.type == BulletType.Convention:
                if attack_radius < max(weapon.shoot_range.values()):
                    attack_radius = max(weapon.shoot_range.values())  # 确定防守算子的攻击半径
        unhidden_ratio_att = calc_unhidden_ratio(obj['type'], attack_loc_area, thread_area_inds, see_matrix,
                                                 dist_matrix, attack_radius)
        attack_locs_attackability = np.zeros(n_hex)
        attack_locs_attackability[attack_loc_area] = unhidden_ratio_att
        defend_locs_attackability = np.max(
            attack_locs_each * np.tile(attack_locs_attackability, (len(defend_locs_inds), 1)), axis=1)
        attackability = (defend_locs_attackability - np.min(defend_locs_attackability)) / (
                np.max(defend_locs_attackability) - np.min(defend_locs_attackability))
        # 2.计算隐蔽性（0.4）
        unhidden_ratio = calc_unhidden_ratio(obj['type'], defend_locs_inds, thread_area_inds, see_matrix, dist_matrix)
        invisibility = (np.max(unhidden_ratio) - unhidden_ratio) / (np.max(unhidden_ratio) - np.min(unhidden_ratio))
        # 3.计算临近性（0.1）：到当前位置的距离
        distances = dist_matrix[mapid_2_matind(obj['cur_hex'], map_size), defend_locs_inds.reshape(1, -1)].reshape(-1)
        proximity = (np.max(distances) - distances) / (np.max(distances) - np.min(distances))
        # 综合计算
        total_measure = 0.3 * attackability + 0.5 * invisibility + 0.2 * proximity
    best_defend_loc_ind = defend_locs_inds[np.argmax(total_measure)]
    best_defend_loc = matind_2_mapid(best_defend_loc_ind, map_size)
    return best_defend_loc


def infer_support_loc(obj_id, tar_area, myAgent):
    """ 2020-7-18
    为战车算子obj计算出针对目标区域tar_area的引导射击支援点位
    """
    if not tar_area:
        return None
    map_size = myAgent.get_map_size()
    n_hexes = int(map_size['row'] * map_size['col'])
    dist_matrix = myAgent.my_map['dist_matrix']
    cost_matrix = myAgent.my_map['cost_matrix'][0]
    ifv = myAgent.get_bop(obj_id)
    # ifv = obj_id  # 调试用
    cur_hex = ifv['cur_hex']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    unit_range = 200. / (ifv['basic_speed'] / 3.6)
    distance = 20  # 重型导弹的射击范围是20
    tar_area_ind = mapid_2_matind(np.array(tar_area), map_size)
    # 与距离相关的攻击等级
    att_level = np.zeros(map_size['row'] + map_size['col'])
    att_level[:21] = np.array([0, 0, 5, 5, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8])
    # 找出候选区域
    candidate_locs_bool = np.any(dist_matrix[tar_area_ind] <= distance, axis=0)
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    # 1. 计算候选点位的平均攻击性
    att_level_matrix = att_level[dist_matrix[candidate_locs.reshape(-1, 1), tar_area_ind.reshape(1, -1)]]
    avg_attackbilities = np.mean(att_level_matrix, axis=1)
    avg_attackbilities_norm = avg_attackbilities / 8
    # 2. 计算候选点位的隐蔽性（我方的被观察范围内是否有敌人？）
    observed_area_bool = get_opr_observed_range_4_locations(ifv['type'], candidate_locs, myAgent)
    observed_area_all = np.arange(n_hexes)[np.any(observed_area_bool, axis=0)]
    enemy_vehicle_locs, enemy_infantry_locs = [], []
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        if not enemy_info['spread_flag']:
            continue
        enemy = enemy_info['operator']
        if enemy['type'] == BopType.Vehicle:
            enemy_vehicle_locs.append(enemy['cur_hex'])
        elif enemy['type'] == BopType.Infantry:
            enemy_infantry_locs.append(enemy['cur_hex'])
    observed_area_min_costs_enemy = np.ones(observed_area_all.size, dtype=float) * 1000.
    if enemy_vehicle_locs:
        enemy_vehicle_locs_ind = mapid_2_matind(np.array(enemy_vehicle_locs), map_size)  # 车辆的当前位置
        observed_area_costs_vehicle = cost_matrix[observed_area_all.reshape(-1, 1), enemy_vehicle_locs_ind.reshape(1, -1)]
        observed_area_min_costs_vehicle = np.min(observed_area_costs_vehicle, axis=1)
        observed_area_min_costs_enemy = np.minimum(observed_area_min_costs_enemy, observed_area_min_costs_vehicle)
    if enemy_infantry_locs:
        enemy_infantry_locs_ind = mapid_2_matind(np.array(enemy_infantry_locs), map_size)  # 步兵的当前位置
        observed_area_costs_infantry = myAgent.my_map['cost_matrix'][1][observed_area_all.reshape(-1, 1),
                                                                        enemy_infantry_locs_ind.reshape(1, -1)]
        observed_area_min_costs_infantry = np.min(observed_area_costs_infantry, axis=1)
        observed_area_min_costs_enemy = np.minimum(observed_area_min_costs_enemy, observed_area_min_costs_infantry)
    observed_area_min_costs = np.ones(n_hexes, dtype=float) * 1000.
    observed_area_min_costs[observed_area_all] = observed_area_min_costs_enemy
    candidate_observed_cost = np.tile(observed_area_min_costs, (candidate_locs.size, 1))
    candidate_observed_cost[np.logical_not(observed_area_bool)] = 1000
    candidate_locs_min_cost = np.min(candidate_observed_cost, axis=1)
    max_cost = np.max(candidate_locs_min_cost)
    if max_cost < 1e-8:
        safties_norm = np.zeros(candidate_locs.size)
    else:
        safties_norm = candidate_locs_min_cost / max_cost
    # 3. 计算候选点位的临近性
    costs = cost_matrix[cur_hex_ind, candidate_locs.reshape(1, -1)]
    costs[costs > 0] += 40.  # 尽量待在原地
    max_cost = 440.
    costs[costs >= max_cost] = max_cost
    proximities_norm = 1 - costs / max_cost
    # 综合评估：
    total_measures = 0.3 * avg_attackbilities_norm + 0.3 * safties_norm + 0.4 * proximities_norm
    # 根据综合评估选择出最佳点位
    best_loc_ind = candidate_locs[np.argmax(total_measures)]
    return matind_2_mapid(best_loc_ind, map_size)


def infer_scout_loc(obj_id, tar_area, myAgent):
    """ 2020-5-7
    为战车算子obj计算出针对目标区域tar_area的侦察点位
    """
    if not tar_area:
        return None
    map_size = myAgent.get_map_size()
    n_hexes = int(map_size['row'] * map_size['col'])
    dist_matrix = myAgent.my_map['dist_matrix']
    see_matrix = myAgent.my_map['see_matrix'][0]
    obj = myAgent.get_bop(obj_id)
    # obj = obj_id  # 调试
    unit_range = 200. / (obj['basic_speed'] / 3.6)
    if obj['type'] == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj['type'] == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        cost_matrix = dist_matrix * (200 / (obj['basic_speed'] / 3.6))
    cur_hex = obj['cur_hex']
    cur_hex_ind = mapid_2_matind(cur_hex, map_size)
    distance = 25
    # 从目标区域每个格子的通视区域，初步筛选出可能的侦察点位
    tar_area_ind = mapid_2_matind(np.array(tar_area), map_size)
    candidate_locs_bool = np.any(dist_matrix[tar_area_ind] <= distance, axis=0)
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    # 1. 计算候选点位的通视率
    multiplies = np.ones(tar_area_ind.size, dtype=float)
    observe_dists = np.ones(tar_area_ind.size, dtype=int) * distance
    terrain_vec = myAgent.my_map['terrain_vec'][tar_area_ind]
    good_terrains = (terrain_vec == TerrainType.Forest) | (terrain_vec == TerrainType.Resident)
    multiplies[good_terrains] = 2.
    observe_dists = observe_dists / multiplies
    unhidden_matrix = (dist_matrix[candidate_locs.reshape(-1, 1), tar_area_ind.reshape(1, -1)] <= observe_dists.reshape(1, -1)) & \
                      (see_matrix[candidate_locs.reshape(-1, 1), tar_area_ind.reshape(1, -1)])
    observibilities_norm = np.mean(unhidden_matrix + 0, axis=1, dtype=float)
    # observibilities_norm = observibilities_norm.reshape(-1)
    # 2. 计算候选点位的隐蔽性
    threat_area_bool = (myAgent.agenda.rae_situation.enemy_threat[0] >= 4)
    threat_area = np.arange(n_hexes)[threat_area_bool]
    if threat_area.size <= 0:
        safties_norm = np.ones(candidate_locs.size)
    else:
        saft_distances = cost_matrix[candidate_locs.reshape(-1, 1), threat_area.reshape(1, -1)]
        min_saft_distances = np.min(saft_distances, axis=1)
        dist_threshold = unit_range * 10  # 这个距离以上的安全性和这个距离的安全性设置相同
        min_saft_distances[min_saft_distances > dist_threshold] = dist_threshold
        safties_norm = np.sqrt(min_saft_distances / dist_threshold)
    # safties_norm = safties_norm.reshape(-1)
    # 3. 计算候选点位的临近性
    costs = cost_matrix[cur_hex_ind, candidate_locs.reshape(1, -1)]
    costs[costs > 0] += 40.  # 尽量待在原地
    max_cost = 440.
    costs[costs >= max_cost] = max_cost
    proximities_norm = 1 - costs / max_cost
    # proximities_norm = proximities_norm.reshape(-1)
    # 综合评估：
    total_measures = 0.3 * observibilities_norm + 0.5 * proximities_norm + 0.3 * safties_norm
    # 根据综合评估选择出最佳点位
    ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc


def infer_unload_loc(ifv_id, myAgent):
    """ 2020-5-19
    推理附近安全的下车点（无人战车、巡飞弹）
    """
    ifv = myAgent.get_bop(ifv_id)
    map_size = myAgent.get_map_size()
    map_hex_num = map_size['row'] * map_size['col']
    radius = 3  # 搜索半径，基于basic_speed的标准地形
    time_limit = 200. / (ifv['basic_speed'] / 3.6) * radius
    # 1.在time_limit的限定下，确定搜索范围search_range
    if ifv['type'] == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif ifv['type'] == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        raise Exception('本函数不适用于当前类型的算子！')
    c_hex = ifv['move_path'][0] if is_moving(ifv) else ifv['cur_hex']
    c_ind = mapid_2_matind(c_hex, map_size)
    search_range = np.arange(map_hex_num)[cost_matrix[c_ind, :] <= time_limit]  # 用mat_ind表示
    # 2.计算search_range中六角格在当前态势下的隐蔽性
    enemies_id = confirm_enemies(myAgent, radius=2)
    enemies_loc = []
    for ene_id in enemies_id:
        enemies_loc.append(myAgent.agenda.get_enemy_info(ene_id)['operator']['cur_hex'])
    if enemies_loc:
        tar_area = mapid_2_matind(enemies_loc, map_size)
        hide_ratio = 1 - calc_unhidden_ratio(ifv['type'], search_range, tar_area, myAgent.my_map['see_matrix'][0],
                                             myAgent.my_map['dist_matrix'])
    else:
        hide_ratio = np.ones(len(search_range))  # 没有敌人时，都有最好的隐蔽性
    # 3.计算search_range中六角格距中心六角格c_hex的最短机动耗时
    cost_time = cost_matrix[c_ind][search_range]
    cost_time_norm = (np.max(cost_time) - cost_time) / (np.max(cost_time) - np.min(cost_time))
    # 4.综合评估：隐蔽性（0.8），机动性（0.2）
    total_result = 0.8 * hide_ratio + 0.2 * cost_time_norm
    # 5.根据综合评估选择出最佳下车点，如有多个，随机选择
    best_loc = matind_2_mapid(np.random.choice(search_range[total_result == np.max(total_result)]), map_size)
    return best_loc


def infer_counterattack_loc(obj_id, myAgent):
    """ 2020-5-20
    推理有组织机动过程中的反击点位
    """
    obj = myAgent.get_bop(obj_id)
    map_size = myAgent.get_map_size()
    map_hex_num = map_size['row'] * map_size['col']
    radius = 3  # 搜索半径，基于basic_speed的标准地形
    weapons_id = obj['carry_weapon_ids']
    tar_type = BopType.Vehicle  # 目前只考虑目标是车辆，后期修改
    distance = 0
    for wp_id in weapons_id:
        dist_wp = get_weapon(wp_id).shoot_range[tar_type]
        if distance < dist_wp:
            distance = dist_wp
    # 1.在radius的限定下，确定搜索范围search_range
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj['type'] == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj['type'] == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        raise Exception('本函数不适用于当前类型的算子！')
    c_hex = obj['move_path'][0] if is_moving(obj) else obj['cur_hex']
    c_ind = mapid_2_matind(c_hex, map_size)
    search_range = np.arange(map_hex_num)[dist_matrix[c_ind, :] <= radius]  # 用mat_ind表示
    # 2.计算search_range中六角格的攻击效能
    enemies_id = confirm_enemies(myAgent, radius=2)
    enemies_loc = []
    for ene_id in enemies_id:
        enemy = myAgent.agenda.get_enemy_info(ene_id)['operator']
        if myAgent.map.get_distance(enemy['cur_hex'], obj['cur_hex']) <= distance:
            enemies_loc.append(myAgent.agenda.get_enemy_info(ene_id)['operator']['cur_hex'])
    if enemies_loc:
        tar_area = mapid_2_matind(enemies_loc, map_size)
        fire_effect = calc_fire_effect(obj, search_range, tar_area, distance, myAgent.my_map['see_matrix'][0],
                                       myAgent.my_map['dist_matrix'])
    else:
        fire_effect = np.ones(len(search_range))  # 没有敌人时，都有最好的攻击效能
    # 3.计算search_range中六角格的安全性
    safty = np.zeros(len(search_range))
    for m in range(len(safty)):
        safty[m] = calc_safty(matind_2_mapid(search_range[m], map_size), obj['type'], obj['armor'],
                              myAgent.map)
    # 4.计算search_range中六角格距中心六角格c_hex的最短机动耗时
    cost_time = cost_matrix[c_ind][search_range]
    cost_time_norm = (np.max(cost_time) - cost_time) / (np.max(cost_time) - np.min(cost_time))
    # 5.综合评估：攻击效能（0.5），安全性（0.4），机动性（0.1）
    total_result = 0.5 * fire_effect + 0.4 * safty + 0.1 * cost_time_norm
    # 根据综合评估选择出最佳下车点，如有多个，随机选择
    best_loc = matind_2_mapid(np.random.choice(search_range[total_result == np.max(total_result)]), map_size)
    return best_loc


def infer_fire_propulsion_loc(obj_id, myAgent):
    """2020-6-22
    为“火力推进任务”推理火力输出点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    enemies = myAgent.agenda.rae_situation.enemies
    n_enemies = len(enemies)
    if n_enemies <= 0:  # 没有敌人，没有必要火力推进
        return None
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    # 计算点位候选区域和各候选点位对敌方单位的最大攻击伤害
    candidate_locs_bool = np.zeros(n_hexes, dtype=bool)
    for enemy_info in enemies:
        enemy = enemy_info['operator']
        if enemy['type'] == BopType.Aircraft or enemy['sub_type'] == BopType.Artillery:  # 火力推进的目标不是“空中单位”和“炮兵”
            continue
        if obj['sub_type'] == BopType.Helicopter and enemy['type'] == BopType.Infantry:
            continue
        this_range = get_enemy_shooted_range(enemy, obj, myAgent)
        candidate_locs_bool[this_range] = True
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    n_candidates = candidate_locs.size
    if n_candidates == 0:
        return None
    # 1. 计算候选点位的安全性
    if obj_type == BopType.Vehicle:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[0]
    elif obj_type == BopType.Infantry:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[2]
    else:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[3]
    candidate_threats = enemy_threats[candidate_locs]  # 候选点位的受威胁等级
    if obj_type == BopType.Vehicle:
        candidate_threats[candidate_threats > 16] = 16  # 车辆限定最大损伤
        max_threat = 16
    else:
        max_threat = np.max(candidate_threats)
    if max_threat < 1e-8:
        safty_norm = np.ones(n_candidates, dtype=float)
    else:
        safty_norm = (max_threat - candidate_threats) / max_threat
    # 2. 计算算子当前位置到各候选点位的易达性
    obj_loc = mapid_2_matind(obj['cur_hex'], map_size)
    if obj_type == BopType.Vehicle:
        move_costs = myAgent.my_map['cost_matrix'][0][obj_loc, candidate_locs]
        move_costs[move_costs > 0] += 2 * 20
    elif obj_type == BopType.Infantry:
        move_costs = myAgent.my_map['cost_matrix'][1][obj_loc, candidate_locs]
        move_costs[move_costs > 0] += 2 * 144
    else:
        move_costs = myAgent.my_map['dist_matrix'][obj_loc, candidate_locs]
        move_costs[move_costs > 0] += 2
    max_cost = np.max(move_costs)
    if max_cost < 1e-8:
        accessibility_norm = np.ones(n_candidates, dtype=float)
    else:
        accessibility_norm = 1 - move_costs / max_cost
    # 综合评估，选点位
    if obj_type == BopType.Vehicle:
        total_measures = 0.4 * safty_norm + 0.6 * accessibility_norm
    elif obj_type == BopType.Infantry:
        total_measures = 0.2 * safty_norm + 0.8 * accessibility_norm
    else:
        total_measures = 0.95 * safty_norm + 0.05 * accessibility_norm
    best_ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[best_ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc


def infer_assult_loc(obj_id, enemy_ids, myAgent):
    """2020-6-22
    为“火力推进任务”推理火力输出点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    dist_matrix = myAgent.my_map['dist_matrix']
    enemies = []
    for ene_id in enemy_ids:
        enemy_info = myAgent.get_enemy_info(ene_id)
        if not enemy_info:
            continue
        if not enemy_info['spread_flag']:
            continue
        enemies.append(enemy_info['operator'])
    n_enemies = len(enemies)
    if n_enemies <= 0:  # 没有敌人，没有必要火力推进
        return None
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    # 1. 计算攻击性
    special_shoot = 'move_shoot' if obj['sub_type'] == BopType.Tank else None
    attackabilities_full = np.zeros(n_hexes)
    for enemy in enemies:
        if enemy['type'] == BopType.Aircraft or enemy['sub_type'] == BopType.Artillery:  # 目标不是“空中单位”和“炮兵”
            continue
        if obj['sub_type'] == BopType.Helicopter and enemy['type'] == BopType.Infantry:
            continue
        shoot_radius = get_shoot_radius(obj['carry_weapon_ids'], obj['remain_bullet_nums'], enemy['type'], special_shoot)
        enemy_loc_ind = mapid_2_matind(enemy['cur_hex'], map_size)
        possible_shoot_area_bool = np.any(dist_matrix[enemy_loc_ind] <= shoot_radius, axis=0)
        possible_shoot_area = np.arange(n_hexes)[possible_shoot_area_bool]
        attack_levels_this = calc_thread_att_level(myAgent, obj['carry_weapon_ids'], obj['remain_bullet_nums'],
                                          possible_shoot_area, enemy['type'], enemy_loc_ind, obj_type, obj['blood'],
                                          special_shoot=special_shoot)
        if enemy['sub_type'] == BopType.Infantry and enemy['keep'] == 1:
            attack_levels_this = attack_levels_this * 2.
        elif enemy['sub_type'] == BopType.Infantry:
            attack_levels_this = attack_levels_this / 2.
        attackabilities_full[possible_shoot_area] = np.maximum(attackabilities_full[possible_shoot_area],
                                                               attack_levels_this.reshape(-1))
    candidate_locs = np.arange(n_hexes)[attackabilities_full > 0]
    n_candidates = candidate_locs.size
    if n_candidates == 0:
        return None
    attackabilities = attackabilities_full[candidate_locs]
    max_attack, min_attack = np.max(attackabilities), np.min(attackabilities)
    if max_attack - min_attack < 1e-8:
        attackabilities_norm = np.ones(n_candidates)
    else:
        attackabilities_norm = (attackabilities - min_attack) / (max_attack - min_attack)
    # 2. 计算算子当前位置到各候选点位的易达性
    obj_loc = mapid_2_matind(obj['cur_hex'], map_size)
    if obj_type == BopType.Vehicle:
        move_costs = myAgent.my_map['cost_matrix'][0][obj_loc, candidate_locs]
        move_costs[move_costs > 0] += 2 * 20
    elif obj_type == BopType.Infantry:
        move_costs = myAgent.my_map['cost_matrix'][1][obj_loc, candidate_locs]
        move_costs[move_costs > 0] += 2 * 144
    else:
        move_costs = myAgent.my_map['dist_matrix'][obj_loc, candidate_locs]
        move_costs[move_costs > 0] += 2
    max_cost = np.max(move_costs)
    if max_cost < 1e-8:
        accessibility_norm = np.ones(n_candidates, dtype=float)
    else:
        accessibility_norm = 1 - move_costs / max_cost
    # 综合评估，选点位
    if obj['sub_type'] == BopType.Tank:
        total_measures = 0.7 * attackabilities_norm + 0.3 * accessibility_norm
    elif obj_type == BopType.Vehicle:
        total_measures = 0.3 * attackabilities_norm + 0.7 * accessibility_norm
    elif obj_type == BopType.Infantry:
        total_measures = 0.1 * attackabilities_norm + 0.9 * accessibility_norm
    else:
        total_measures = 0.5 * attackabilities_norm + 0.5 * accessibility_norm
    best_ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[best_ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc


def infer_guide_fire_loc(obj_id, myAgent):
    """2020-6-24
    为UCV和UAV推理引导射击点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    obj = myAgent.get_bop(obj_id)
    obj_type = obj['type']
    enemies = []
    for enemy_info in myAgent.agenda.rae_situation.enemies:
        if not enemy_info['spread_flag']:
            continue
        enemy = enemy_info['operator']
        if enemy['sub_type'] not in [BopType.Tank, BopType.IFV]:
            continue
        enemies.append(enemy_info)
    n_enemies = len(enemies)
    if n_enemies <= 0:  # 没有敌人，没有必要引导射击
        # print('\t引导：n_enemies <= 0')
        return None
    # 计算点位候选区域和各候选点位对敌方单位的最大攻击伤害
    if obj_type != BopType.Aircraft:  # 如果是UCV
        candidate_locs_bool = np.zeros(n_hexes, dtype=bool)
        for enemy_info in enemies:
            enemy = enemy_info['operator']
            this_range = get_enemy_observed_range(enemy, obj, myAgent)
            candidate_locs_bool[this_range] = True
        candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
        if candidate_locs.size == 0:  # 没有有效敌人，candidate_locs为空
            return None
    else: # 如果是UAV，考虑步兵，进一步缩小候选区域
        candidate_area_tank, candidate_area_ifv, infantry_threat_area = [], [], []
        for enemy_info in enemies:
            enemy = enemy_info['operator']
            flag_can_guide = False
            for opr in myAgent.get_our_oprs():
                if opr['sub_type'] != BopType.IFV:
                    continue
                if opr['stop'] == 0:
                    continue
                if opr['remain_bullet_nums'][BulletType.HeavyMissile] <= 0:
                    continue
                if cal_distance(opr['cur_hex'], enemy['cur_hex']) > 20:
                    continue
                flag_can_guide = True
            if flag_can_guide:
                # print('\t引导：可引导')
                if enemy['stop'] == 1 or len(enemy_info['trace']) < 2:
                    terrain_type = get_terrain_info(myAgent.map.get_map_data(), enemy['cur_hex'])
                    if enemy['sub_type'] == BopType.Tank:
                        if terrain_type in [TerrainType.Forest, TerrainType.Resident]:
                            candidate_area_tank += get_range(enemy['cur_hex'], 1, map_size)
                        else:
                            candidate_area_tank += get_range(enemy['cur_hex'], 2, map_size)
                    else:
                        if terrain_type in [TerrainType.Forest, TerrainType.Resident]:
                            candidate_area_ifv += get_range(enemy['cur_hex'], 1, map_size)
                        else:
                            candidate_area_ifv += get_range(enemy['cur_hex'], 2, map_size)
                else:  # 预测位置
                    cur_hex, last_hex = enemy['cur_hex'], enemy_info['trace'][1][0]
                    move_direction = get_direction(cur_hex, last_hex)
                    terrain_type = get_terrain_info(myAgent.map.get_map_data(), cur_hex)
                    if terrain_type in [TerrainType.Forest, TerrainType.Resident]:
                        radius = 1
                    else:
                        radius = 2
                    predict_hex = get_hex_in_direction(cur_hex, move_direction, radius)
                    if enemy['sub_type'] == BopType.Tank:
                        candidate_area_tank.append(predict_hex)
                    else:
                        candidate_area_ifv.append(predict_hex)
            elif enemy['type'] == BopType.Infantry:
                if enemy_info['remain_bullet_nums'][BulletType.HeavyMissile] > 0:
                    infantry_threat_area += get_range(enemy['cur_hex'], 1, map_size)
        candidate_area = candidate_area_tank if candidate_area_tank else candidate_area_ifv  # 优先引导敌方坦克
        candidate_area = list(set(candidate_area).difference(set(infantry_threat_area)))
        if not candidate_area:
            # print('\t引导：not candidate_area')
            return None
        candidate_locs = mapid_2_matind(np.array(candidate_area), map_size)
    n_candidates = candidate_locs.size
    # 1. 计算算子当前位置到各候选点位的易达性
    obj_loc = mapid_2_matind(obj['cur_hex'], map_size)
    if obj_type == BopType.Vehicle:
        move_costs = myAgent.my_map['cost_matrix'][0][obj_loc, candidate_locs]
    elif obj_type == BopType.Infantry:
        move_costs = myAgent.my_map['cost_matrix'][1][obj_loc, candidate_locs]
    else:
        move_costs = myAgent.my_map['dist_matrix'][obj_loc, candidate_locs]
    max_cost, min_cost = np.max(move_costs), np.min(move_costs)
    if max_cost - min_cost < 1e-8:
        accessibility_norm = np.ones(n_candidates, dtype=float)
    else:
        accessibility_norm = (max_cost - move_costs) / (max_cost - min_cost)
    # 2. 计算候选点位的安全性
    if obj_type != BopType.Aircraft:
        if obj_type == BopType.Vehicle:
            enemy_threats = myAgent.agenda.rae_situation.enemy_threat[0]
        else:
            enemy_threats = myAgent.agenda.rae_situation.enemy_threat[1]
        candidate_threats = enemy_threats[candidate_locs]  # 候选点位的受威胁等级
        max_threat, min_threat = np.max(candidate_threats), np.min(candidate_threats)
        if obj['sub_type'] == BopType.Drone:
            if min_threat >= 7:
                return None
        if max_threat - min_threat < 1e-8:
            safty_norm = np.ones(n_candidates, dtype=float)
        else:
            safty_norm = (max_threat - candidate_threats) / max_threat
        # 综合评估，选点位
        if obj_type == BopType.Vehicle:
            total_measures = 0.7 * safty_norm + 0.3 * accessibility_norm
        else:  # 步兵
            total_measures = 0.4 * safty_norm + 0.6 * accessibility_norm
    else:
        total_measures = accessibility_norm  # 无人机
    best_ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[best_ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc


def infer_directing_loc(obj_id, tar_loc, remaining_steps, myAgent):
    """2020-7-3
    为炮兵校射算子推理校射点位
    """
    map_size = myAgent.get_map_size()
    n_hexes = map_size['row'] * map_size['col']
    time_threshold = remaining_steps * 0.9
    obj = myAgent.get_bop(obj_id)
    obj_loc_ind = mapid_2_matind(obj['cur_hex'], map_size)
    obj_type = obj['type']
    tar_loc_ind = mapid_2_matind(tar_loc, map_size)
    # 确定cost_matrix
    if obj_type == BopType.Vehicle:
        cost_matrix = myAgent.my_map['cost_matrix'][0]
    elif obj_type == BopType.Infantry:
        cost_matrix = myAgent.my_map['cost_matrix'][1]
    else:
        cost_matrix = myAgent.my_map['dist_matrix'] * 200. / (obj['basic_speed'] / 3.6)
    # 确定see_matrix
    if obj_type == BopType.Aircraft:
        see_matrix = myAgent.my_map['see_matrix'][2]
    else:
        see_matrix = myAgent.my_map['see_matrix'][0]
    # 确定校射类型
    direct_type = 'see_hex'
    if obj['sub_type'] == BopType.Drone:
        flag_infantry = False
        for enemy_info in myAgent.agenda.rae_situation.enemies:
            if not enemy_info['spread_flag']:
                continue
            enemy = enemy_info['operator']
            if enemy['type'] != BopType.Infantry:
                continue
            if cal_distance(enemy['cur_hex'], tar_loc) <= 1:
                flag_infantry = True
                break
        if not flag_infantry:
            direct_type = 'see_target'
    # === 确定观察范围 ===
    dist_matrix = myAgent.my_map['dist_matrix']
    if obj['sub_type'] == BopType.Drone:
        observe_dist = 2
    else:
        observe_dist = 25
    if direct_type == 'see_target':
        terrain_type = get_terrain_info(myAgent.map.get_map_data(), tar_loc)
        if terrain_type in [TerrainType.Forest, TerrainType.Resident]:
            observe_dist = int(observe_dist / 2)
    observe_range_bool = np.logical_and(see_matrix[:, tar_loc_ind], dist_matrix[tar_loc_ind] <= observe_dist)
    if not np.any(observe_range_bool):
        return None
    # 获得威胁态势
    if obj_type == BopType.Vehicle:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[0]
    elif obj_type == BopType.Infantry:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[1]
    elif obj['sub_type'] == BopType.Helicopter:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[2]
    else:
        enemy_threats = myAgent.agenda.rae_situation.enemy_threat[3]
    # === 根据机动距离筛除不可达范围 ===
    candidate_locs_bool = np.logical_and(cost_matrix[obj_loc_ind] <= time_threshold, observe_range_bool)
    if obj['sub_type'] == BopType.Drone:
        candidate_locs_bool = np.logical_and(candidate_locs_bool, enemy_threats < 7)
    candidate_locs = np.arange(n_hexes)[candidate_locs_bool]
    if not np.any(candidate_locs_bool):
        return None
    n_candidates = candidate_locs.size
    # 1. 计算临近性
    move_costs = cost_matrix[obj_loc_ind, candidate_locs.reshape(1, -1)]
    max_cost, min_cost = np.max(move_costs), np.min(move_costs)
    if max_cost - min_cost < 1e-8:
        proximities = np.ones(n_candidates, dtype=float)
    else:
        proximities = (max_cost - move_costs) / max_cost
    # 2. 计算安全性
    candidate_threats = enemy_threats[candidate_locs]  # 候选点位的受威胁等级
    max_threat, min_threat = np.max(candidate_threats), np.min(candidate_threats)
    if max_threat - min_threat < 1e-8:
        safty_norm = np.ones(n_candidates, dtype=float)
    else:
        safty_norm = (max_threat - candidate_threats) / max_threat
    # 综合评估，选点位
    total_measures = 0.7 * safty_norm + 0.3 * proximities
    best_ind = np.argmax(total_measures)
    best_loc_ind = candidate_locs[best_ind]
    best_loc = matind_2_mapid(best_loc_ind, map_size)
    return best_loc
